In this notebook, we specifically compare using Alevin-fry with cellranger to quantify both single-cell and single-nuclei RNA seq data.

For cellranger, we are using the default parameters for single-cell RNA-seq and the --include-introns option for the single-nuclei RNA-seq option, which has been labeled as the splici index for easy comparison with alevin-fry. For Alevin-fry, we are interested in a few different parameters specifically:

  • The use of the splici index vs. the transcriptome index only for single-cell RNA-seq samples
  • Use of the cr-like or full resolution
  • Use of pseudoalignment (sketch) or selective alignment (salign)

Previously, we had found that the sketch performed well, although there was a slight increase in UMIs/cell and genes detected/cell. Data has also surfaced from Dobin et al. in STARsolo: accurate, fast and versatile mapping/quantification of single-cell and single-nucleus RNA-seq data and in an alevin-fry tutorial indicating pseudoaligners have a tendency to result in false detection of increased gene expression. Use of the splici index with alevin-fry has been reported to decrease this false positive expression.

More about use of the splici index and different resolutions can be found in the pre-print on Alevin-fry.

We will be testing the following conditions of alevin-fry:

  • spliced (cDNA) txome, salign, full
  • spliced (cDNA) txome, sketch, full
  • spliced (cDNA) txome, salign, cr-like
  • spliced (cDNA) txome, sketch, cr-like
  • unspliced (splici) txome, salign, full
  • unspliced (splici) txome, sketch, full
  • unspliced (splici) txome, salign, cr-like
  • unspliced (splici) txome, sketch, cr-like

There are three single-cell samples (SCPCR000006, SCPCR000126, SCPCR000127) and four single-nuclei samples that were used for comparisons (SCPCR000118, SCPCR000119, SCPCR000220, SCPCR000221).

Setup

library(magrittr)
library(ggplot2)
library(SingleCellExperiment)
Loading required package: SummarizedExperiment
Loading required package: MatrixGenerics
Loading required package: matrixStats

Attaching package: ‘MatrixGenerics’

The following objects are masked from ‘package:matrixStats’:

    colAlls, colAnyNAs, colAnys, colAvgsPerRowSet, colCollapse, colCounts,
    colCummaxs, colCummins, colCumprods, colCumsums, colDiffs, colIQRDiffs, colIQRs,
    colLogSumExps, colMadDiffs, colMads, colMaxs, colMeans2, colMedians, colMins,
    colOrderStats, colProds, colQuantiles, colRanges, colRanks, colSdDiffs, colSds,
    colSums2, colTabulates, colVarDiffs, colVars, colWeightedMads, colWeightedMeans,
    colWeightedMedians, colWeightedSds, colWeightedVars, rowAlls, rowAnyNAs, rowAnys,
    rowAvgsPerColSet, rowCollapse, rowCounts, rowCummaxs, rowCummins, rowCumprods,
    rowCumsums, rowDiffs, rowIQRDiffs, rowIQRs, rowLogSumExps, rowMadDiffs, rowMads,
    rowMaxs, rowMeans2, rowMedians, rowMins, rowOrderStats, rowProds, rowQuantiles,
    rowRanges, rowRanks, rowSdDiffs, rowSds, rowSums2, rowTabulates, rowVarDiffs,
    rowVars, rowWeightedMads, rowWeightedMeans, rowWeightedMedians, rowWeightedSds,
    rowWeightedVars

Loading required package: GenomicRanges
Loading required package: stats4
Loading required package: BiocGenerics
Loading required package: parallel

Attaching package: ‘BiocGenerics’

The following objects are masked from ‘package:parallel’:

    clusterApply, clusterApplyLB, clusterCall, clusterEvalQ, clusterExport,
    clusterMap, parApply, parCapply, parLapply, parLapplyLB, parRapply, parSapply,
    parSapplyLB

The following objects are masked from ‘package:stats’:

    IQR, mad, sd, var, xtabs

The following objects are masked from ‘package:base’:

    anyDuplicated, append, as.data.frame, basename, cbind, colnames, dirname,
    do.call, duplicated, eval, evalq, Filter, Find, get, grep, grepl, intersect,
    is.unsorted, lapply, Map, mapply, match, mget, order, paste, pmax, pmax.int,
    pmin, pmin.int, Position, rank, rbind, Reduce, rownames, sapply, setdiff, sort,
    table, tapply, union, unique, unsplit, which.max, which.min

Loading required package: S4Vectors

Attaching package: ‘S4Vectors’

The following object is masked from ‘package:base’:

    expand.grid

Loading required package: IRanges
Loading required package: GenomeInfoDb
Loading required package: Biobase
Welcome to Bioconductor

    Vignettes contain introductory material; view with 'browseVignettes()'. To cite
    Bioconductor, see 'citation("Biobase")', and for packages 'citation("pkgname")'.


Attaching package: ‘Biobase’

The following object is masked from ‘package:MatrixGenerics’:

    rowMedians

The following objects are masked from ‘package:matrixStats’:

    anyMissing, rowMedians
# load in benchmarking functions 
function_path <- file.path("..", "benchmarking-functions", "R")
file.path(function_path, list.files(function_path, pattern = ".R$")) %>%
  purrr::walk(source)
```r
# path to results files with sces and qc dataframes 
base_dir <- here::here()
file_dir <- file.path(base_dir, \results\)

# sce files 
cDNA_salign_full_file <- file.path(file_dir, \cDNA_salign_full_sces.rds\)
cDNA_sketch_full_file <- file.path(file_dir, \cDNA_sketch_full_sces.rds\)
cDNA_salign_cr_file <- file.path(file_dir, \cDNA_salign_cr_sces.rds\)
cDNA_sketch_cr_file <- file.path(file_dir, \cDNA_sketch_cr_sces.rds\)
splici_salign_full_file <- file.path(file_dir, \splici_salign_full_sces.rds\)
splici_sketch_full_file <- file.path(file_dir, \splici_sketch_full_sces.rds\)
splici_salign_cr_file <- file.path(file_dir, \splici_salign_cr_sces.rds\)
splici_sketch_cr_file <- file.path(file_dir, \splici_sketch_cr_sces.rds\)
cellranger_file <- file.path(file_dir, \cellranger_sces.rds\)

# qc files 

quant_info_file <- file.path(file_dir, \quant_info.tsv\)
coldata_df_file <- file.path(file_dir, \coldata_qc.tsv\)
rowdata_df_file <- file.path(file_dir, \rowdata_qc.tsv\)

# mito gene list 
mito_file <- file.path(base_dir, \sample-info\, \Homo_sapiens.GRCh38.103.mitogenes.txt\)

<!-- rnb-source-end -->

<!-- rnb-chunk-end -->


<!-- rnb-text-begin -->




<!-- rnb-text-end -->


<!-- rnb-chunk-begin -->


<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxuIyByZWFkIGluIHNjZXNcbmNETkFfc2FsaWduX2Z1bGwgPC0gcmVhZHI6OnJlYWRfcmRzKGNETkFfc2FsaWduX2Z1bGxfZmlsZSlcbmNETkFfc2tldGNoX2Z1bGwgPC0gcmVhZHI6OnJlYWRfcmRzKGNETkFfc2tldGNoX2Z1bGxfZmlsZSlcbmNETkFfc2FsaWduX2NyIDwtIHJlYWRyOjpyZWFkX3JkcyhjRE5BX3NhbGlnbl9jcl9maWxlKVxuY0ROQV9za2V0Y2hfY3IgPC0gcmVhZHI6OnJlYWRfcmRzKGNETkFfc2tldGNoX2NyX2ZpbGUpXG5zcGxpY2lfc2FsaWduX2Z1bGwgPC0gcmVhZHI6OnJlYWRfcmRzKHNwbGljaV9zYWxpZ25fZnVsbF9maWxlKVxuc3BsaWNpX3NrZXRjaF9mdWxsIDwtIHJlYWRyOjpyZWFkX3JkcyhzcGxpY2lfc2tldGNoX2Z1bGxfZmlsZSlcbnNwbGljaV9zYWxpZ25fY3IgPC0gcmVhZHI6OnJlYWRfcmRzKHNwbGljaV9zYWxpZ25fY3JfZmlsZSlcbnNwbGljaV9za2V0Y2hfY3IgPC0gcmVhZHI6OnJlYWRfcmRzKHNwbGljaV9za2V0Y2hfY3JfZmlsZSlcbmNlbGxyYW5nZXIgPC0gcmVhZHI6OnJlYWRfcmRzKGNlbGxyYW5nZXJfZmlsZSlcblxuIyBtYWtlIGEgbGlzdCB0aGF0IHdpbGwgYmUgdXNlZCBsYXRlciBmb3IgY2FsY3VsYXRpbmcgcWMgd2l0aCBhIHNwZWNpZmljIHRocmVzaG9sZFxuc2NlX2xpc3QgPC0gbGlzdChcbiAgY0ROQV9zYWxpZ25fZnVsbCA9IGNETkFfc2FsaWduX2Z1bGwsXG4gIGNETkFfc2tldGNoX2Z1bGwgPSBjRE5BX3NrZXRjaF9mdWxsLFxuICBjRE5BX3NhbGlnbl9jciA9IGNETkFfc2FsaWduX2NyLFxuICBjRE5BX3NrZXRjaF9jciA9IGNETkFfc2tldGNoX2NyLFxuICBzcGxpY2lfc2FsaWduX2Z1bGwgPSBzcGxpY2lfc2FsaWduX2Z1bGwsXG4gIHNwbGljaV9za2V0Y2hfZnVsbCA9IHNwbGljaV9za2V0Y2hfZnVsbCxcbiAgc3BsaWNpX3NhbGlnbl9jciA9IHNwbGljaV9zYWxpZ25fY3IsXG4gIHNwbGljaV9za2V0Y2hfY3IgPSBzcGxpY2lfc2tldGNoX2NyLFxuICBjZWxscmFuZ2VyID0gY2VsbHJhbmdlclxuKVxuYGBgIn0= -->

```r
# read in sces
cDNA_salign_full <- readr::read_rds(cDNA_salign_full_file)
cDNA_sketch_full <- readr::read_rds(cDNA_sketch_full_file)
cDNA_salign_cr <- readr::read_rds(cDNA_salign_cr_file)
cDNA_sketch_cr <- readr::read_rds(cDNA_sketch_cr_file)
splici_salign_full <- readr::read_rds(splici_salign_full_file)
splici_sketch_full <- readr::read_rds(splici_sketch_full_file)
splici_salign_cr <- readr::read_rds(splici_salign_cr_file)
splici_sketch_cr <- readr::read_rds(splici_sketch_cr_file)
cellranger <- readr::read_rds(cellranger_file)

# make a list that will be used later for calculating qc with a specific threshold
sce_list <- list(
  cDNA_salign_full = cDNA_salign_full,
  cDNA_sketch_full = cDNA_sketch_full,
  cDNA_salign_cr = cDNA_salign_cr,
  cDNA_sketch_cr = cDNA_sketch_cr,
  splici_salign_full = splici_salign_full,
  splici_sketch_full = splici_sketch_full,
  splici_salign_cr = splici_salign_cr,
  splici_sketch_cr = splici_sketch_cr,
  cellranger = cellranger
)
# read in dataframes needed for plotting
quant_info <- readr::read_tsv(quant_info_file)
Rows: 47 Columns: 13
── Column specification ────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (10): tool, quant_dir, sample, index_type, alevin_alignment, alevin_resolution, filter_s...
lgl  (3): filter, usa_mode, intron_mode

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
coldata_df <- readr::read_tsv(coldata_df_file)
Rows: 633188 Columns: 10
── Column specification ────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (3): quant_id, cell_id, tool
dbl (7): sum, detected, subsets_mito_sum, subsets_mito_detected, subsets_mito_percent, total...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
rowdata_df <- readr::read_tsv(rowdata_df_file)
Rows: 2476482 Columns: 8
── Column specification ────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (6): quant_id, gene_id, tool, ID, Symbol, Type
dbl (2): mean, detected

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# load in mito genes file used later 
mito_genes <- readr::read_tsv(mito_file, col_names = "gene_id")
Rows: 111 Columns: 1
── Column specification ────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (1): gene_id

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
mito_genes <- mito_genes %>%
  dplyr::pull(gene_id) %>%
  unique()
# prep the dataframes for plotting
## fix error for 220 and 221 run with cellranger that should have index_type as splici 
quant_info[which(quant_info$quant_dir == "SCPCR000220-cdna-pre_mRNA"),"index_type"] <- "splici"
quant_info[which(quant_info$quant_dir == "SCPCR000221-cdna-pre_mRNA"),"index_type"] <- "splici"

# merge coldata df with quant_info
coldata_info_df <- coldata_df %>%
  dplyr::mutate(tool = dplyr::case_when(tool == "alevin-fry-unfiltered" ~ "alevin-fry",
                                        tool == "cellranger" ~ "cellranger")) %>%
  dplyr::left_join(quant_info,
                   by = c("tool" = "tool", 
                          "quant_id" = "quant_dir")) %>%
  # rename the not_alevin to cellranger for plotting purposes
  dplyr::mutate(alevin_resolution = ifelse(alevin_resolution == "not_alevin", "cellranger", alevin_resolution))

Comparison of QC Metrics

Mitochondrial Content

First, we will start by just looking at mitochondrial content across each of the tools.

ggplot(coldata_info_df, aes(x = tool, y = subsets_mito_percent, fill = tool)) + 
  geom_boxplot() + 
  facet_grid(sample ~ index_type) + 
  theme_classic() + 
  ylab("% Mito /Cell") + 
  theme(axis.ticks.x = element_blank(), axis.text.x = element_text(angle = 45, hjust = 1))

It looks like generally mitochondrial content is uniform and low across all tools and all samples, which is great.

Before doing anymore plotting, let’s split our dataframe by single-cell and single-nucleus RNA-seq samples.

cell_coldata_qc <- coldata_info_df %>%
  dplyr::filter(seq_unit == "cell")

nucleus_coldata_qc <- coldata_info_df %>%
  dplyr::filter(seq_unit == "nucleus") %>%
  dplyr::filter(index_type == "splici")

Per Cell QC Metrics in all cells

We are going to look at some QC metrics at a per cell level. Specifically we will look at UMI/cell and genes detected/cell.

ggplot(nucleus_coldata_qc, aes(x = alevin_resolution, y = sum, fill = alevin_alignment)) + 
  geom_boxplot() + 
  facet_grid(~ sample) +
  theme_classic() + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  ylab("UMI/cell") + 
  xlab("") +
  coord_cartesian(ylim = c(0,30000))

ggplot(cell_coldata_qc, aes(x = alevin_resolution, y = sum, fill = alevin_alignment)) + 
  geom_boxplot() + 
  facet_grid(sample ~ index_type) +
  theme_classic() + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  ylab("UMI/cell") + 
  xlab("") + 
  ylim(c(0,50000))

In looking at UMI/cell between cell and nucleus samples, it’s very clear to me that the single-nuclei samples have a bit more fluctuation - both across samples and across tools. The single-cell samples tend to be fairly uniform across all tools, although it looks like the median in 126 and 127 is lower in alevin-fry than in cellranger. This is not the case in the single-nuclei samples where we see that alevin-fry doesn’t seem to be capturing as many counts, specifically in sample 220.

ggplot(nucleus_coldata_qc, aes(x = alevin_resolution, y = detected, fill = alevin_alignment)) + 
  geom_boxplot() + 
  facet_grid(~ sample) +
  theme_classic() + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  ylab("Genes detected/cell") + 
  xlab("") +
  coord_cartesian(ylim = c(0,15000))

Here, we see even more variation in genes detected per cell in the single-nuclei samples with cellranger tending to have much tighter IQR’s than alevin-fry. However, we are still looking at all cells and not just shared cells only, so that could be part of the issue. Alevin-fry could be detecting more cells with lower counts than cellranger.

ggplot(cell_coldata_qc, aes(x = alevin_resolution, y = detected, fill = alevin_alignment)) + 
  geom_boxplot() + 
  facet_grid(sample ~ index_type) +
  theme_classic() + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  ylab("Genes detected/cell") + 
  xlab("") + 
  ylim(c(0,10000))

Per Cell QC Metrics in Shared Cells only, No Minimum Gene Coverage

Now let’s look at the same metrics but in shared cells only to see if any of these fluctuations across tools are because different cells are being identified.

# filter for cells that are found in all configurations of alevin + cellranger
cell_counts <- cell_coldata_qc %>%  
  dplyr::count(cell_id, sample)

common_cells <- cell_counts %>%
  dplyr::filter(n == 9) %>%
  dplyr::pull(cell_id)

cell_qc_common <- cell_coldata_qc %>%
  dplyr::filter(cell_id %in% common_cells) 
# filter for cells that are found in all configurations of alevin + cellranger
nuclei_counts <- nucleus_coldata_qc %>%
  dplyr::count(cell_id, sample)

common_nuclei <- nuclei_counts %>%
  dplyr::filter(n == 5) %>%
  dplyr::pull(cell_id)

nucleus_qc_common <- nucleus_coldata_qc %>%
  dplyr::filter(
    (cell_id %in% common_nuclei)
  )

Does mitochondrial content change when we only look at shared data? I expect not since it was already pretty uniform.

# mito comparison across shared cells only of all runs
# nucleus samples first 
ggplot(nucleus_qc_common, aes(x = alevin_resolution, y = subsets_mito_percent, fill = tool)) + 
  geom_boxplot() + 
  facet_grid(~ sample) + 
  theme_classic() + 
  ylab("% Mito /Cell") + 
  theme(axis.ticks.x = element_blank(), axis.text.x = element_blank())


# single cell
ggplot(cell_qc_common, aes(x = alevin_resolution, y = subsets_mito_percent, fill = tool)) + 
  geom_boxplot() + 
  facet_grid(sample ~ index_type) + 
  theme_classic() + 
  ylab("% Mito /Cell") + 
  theme(axis.ticks.x = element_blank(), axis.text.x = element_blank())

The answer is no, it seems to still be quite similar, although here I am plotting it by breaking out the single-nuclei samples and you can see that SCPCR000118 has higher mito content than the other samples and fluctuates across tools. In the previous benchmarking I was worried about this sample not being high quality and although technically the mito content is still below 20%, it doesn’t look as uniform as in 220 or 221.

Let’s look at UMI/cell and genes detected/cell in the shared cells.

ggplot(nucleus_qc_common, aes(x = alevin_resolution, y = sum, fill = alevin_alignment)) + 
  geom_boxplot() + 
  facet_grid(~ sample) +
  theme_classic() + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  ylab("UMI/cell") + 
  xlab("") +
  coord_cartesian(ylim = c(0,30000))

ggplot(cell_qc_common, aes(x = alevin_resolution, y = sum, fill = alevin_alignment)) + 
  geom_boxplot() + 
  facet_grid(sample ~ index_type) +
  theme_classic() + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  ylab("UMI/cell") + 
  xlab("") + 
  ylim(c(0,50000))

ggplot(nucleus_qc_common, aes(x = alevin_resolution, y = detected, fill = alevin_alignment)) + 
  geom_boxplot() + 
  facet_grid(~ sample) +
  theme_classic() + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  ylab("Genes detected/cell") + 
  xlab("") +
  coord_cartesian(ylim = c(0,15000))

ggplot(cell_qc_common, aes(x = alevin_resolution, y = detected, fill = alevin_alignment)) + 
  geom_boxplot() + 
  facet_grid(sample ~ index_type) +
  theme_classic() + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  ylab("Genes detected/cell") + 
  xlab("") +
  coord_cartesian(ylim = c(0,10000))

Overall, it looks like we have eliminated many of the differences between the tools in the single-nuclei RNA-seq data. Although it appears that across the board the cr-like resolution gives more on par distributions to cellranger than the full resolution. The full resolution seems to have higher UMIs/cell and genes/cell across most samples. Another consistent finding is that the --sketch or pseudoalignment is giving higher UMIs/cell and genes detected/cell across the board, although this is more noticeable in the single-nuclei samples. It also seems to be more apparent with the full resolution which is probably to be expected since the full resolution is not throwing out multi-mapped genes like the cr-like resolution will.

It does look like alevin-fry is detecting more cells than cellranger, so perhaps there is something we could be doing at the filtering stage that could help improve this?

Let’s take a quick look at the number of cells detected by tool just to be sure that this hypothesis is correct.

cell_numbers <- coldata_info_df %>% 
  dplyr::group_by(quant_id, tool, sample) %>% 
  #dplyr::filter(sample != "SCPCR000118") %>%
  dplyr::tally()

ggplot(cell_numbers, aes(x = sample, y = n, color = tool)) + 
  geom_point() +
  theme_classic()

As expected, for all samples (except SCPCR000006), cellranger is detecting fewer cells than in alevin-fry. I’m also not sure what has happened with SCPCR000118, but we saw this previously and it also shows strange patterns in mitochondrial content and very low coverage across the board.

Per Cell QC Metrics in Shared Cells, Minimum Gene Coverage

To see if the increase in genes detected/cell in the full resolution could be due to low covered genes, let’s look at the number of genes detected if we were to remove genes only found in < 5% of cells.

# use addPerCellQC with gene detection threshold
# only genes with detection in > 5% of cells will be included in the QC calculations
sce_threshold_list <- sce_list %>%
  purrr::map(
    ~ purrr::map(.x, scpcaTools::add_cell_mito_qc, mito = mito_genes, threshold = 5)
  )
Loading required package: HDF5Array
Loading required package: DelayedArray
Loading required package: Matrix

Attaching package: ‘Matrix’

The following object is masked from ‘package:S4Vectors’:

    expand


Attaching package: ‘DelayedArray’

The following objects are masked from ‘package:base’:

    aperm, apply, rowsum

Loading required package: rhdf5
# change names of the sce list to be the tool used first
names(sce_threshold_list) <- c(rep("alevin-fry", 8), "cellranger")

# merge back into a dataframe for plotting 
coldata_threshold <- purrr::map_df(
  sce_threshold_list,
  ~ purrr::map_df(.x, scpcaTools::coldata_to_df, .id = "quant_id"), 
  .id = "tool"
)
# merge new coldata back with quant_info
coldata_info_threshold_df <- coldata_threshold %>%
  dplyr::left_join(quant_info,
                   by = c("tool" = "tool", 
                          "quant_id" = "quant_dir")) %>%
  # rename the not_alevin to cellranger for plotting purposes
  dplyr::mutate(alevin_resolution = ifelse(alevin_resolution == "not_alevin", "cellranger", alevin_resolution))

# break out into single cell and single nucleus 
cell_coldata_threshold_qc <- coldata_info_threshold_df %>%
  dplyr::filter(seq_unit == "cell")

nucleus_coldata_threshold_qc <- coldata_info_threshold_df %>%
  dplyr::filter(seq_unit == "nucleus" & index_type == "splici")
# look for shared cells only 
cell_counts_threshold <- cell_coldata_threshold_qc %>%  
  dplyr::count(cell_id, sample)

common_cells_threshold <- cell_counts_threshold %>%
  dplyr::filter(n == 9) %>%
  dplyr::pull(cell_id)

cell_qc_common_threshold <- cell_coldata_threshold_qc %>%
  dplyr::filter(
    (cell_id %in% common_cells_threshold) 
  )
# look for shared cells only 
nuclei_counts_threshold <- nucleus_coldata_threshold_qc %>%
  dplyr::count(cell_id, sample)

common_nuclei_threshold <- nuclei_counts_threshold %>%
  dplyr::filter(n == 5) %>%
  dplyr::pull(cell_id)

nucleus_qc_common_threshold <- nucleus_coldata_threshold_qc %>%
  dplyr::filter(
    (cell_id %in% common_nuclei_threshold)
  )
ggplot(nucleus_qc_common_threshold, aes(x = alevin_resolution, y = detected, fill = alevin_alignment)) + 
  geom_boxplot() + 
  facet_grid(~ sample) +
  theme_classic() + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  ylab("Genes detected/cell") + 
  xlab("") +
  coord_cartesian(ylim = c(0,15000))

ggplot(cell_qc_common_threshold, aes(x = alevin_resolution, y = detected, fill = alevin_alignment)) + 
  geom_boxplot() + 
  facet_grid(sample ~ index_type) +
  theme_classic() + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  ylab("Genes detected/cell") + 
  xlab("") +
  coord_cartesian(ylim = c(0,10000))

It does not appear that the increase in genes detected observed in the full resolution is resolved by removing lowly covered genes.

Per Cell Correlations across tools

The last comparison we will make at the per cell level is to look at the correlation of UMI/cell across tools. Here we will compare each of the alevin-fry configurations to cellranger and look at the correlation coefficient and plot some of the individual values of UMI/cell to see how well correlated these tools are.

# spread table for comparisons
cell_qc_common_cor <- cell_qc_common %>%
  dplyr::select(tool, index_type, alevin_resolution, alevin_alignment, cell_id, sample, sum) %>%
  # spread the mean expression stats to one column per caller
  tidyr::pivot_wider(id_cols = c(cell_id, sample),
                     names_from = c("tool", "index_type", "alevin_resolution", "alevin_alignment"),
                     values_from = sum) %>%
  # drop rows with NA values to ease correlation calculations
  tidyr::drop_na()

nucleus_qc_common_cor <- nucleus_qc_common %>%
  dplyr::select(tool, index_type, alevin_resolution, alevin_alignment, cell_id, sample, sum) %>%
  tidyr::pivot_wider(id_cols = c(cell_id, sample),
                     names_from = c("tool", "index_type", "alevin_resolution", "alevin_alignment"),
                     values_from = sum) %>%
  tidyr::drop_na()
cell_qc_common_cor %>% 
  dplyr::group_by(sample) %>%
  dplyr::summarize(
    cDNA_salign_full_cor = cor(`cellranger_cDNA_cellranger_not_alevin`, `alevin-fry_cDNA_full_salign`, method = "spearman"),
    cDNA_sketch_full_cor =cor(`cellranger_cDNA_cellranger_not_alevin`, `alevin-fry_cDNA_full_sketch`, method = "spearman"),
    cDNA_salign_cr_cor = cor(`cellranger_cDNA_cellranger_not_alevin`, `alevin-fry_cDNA_cr_salign`, method = "spearman"),
    cDNA_sketch_cr_cor = cor(`cellranger_cDNA_cellranger_not_alevin`, `alevin-fry_cDNA_cr_sketch`, method = "spearman"), 
    splici_salign_full_cor = cor(`cellranger_cDNA_cellranger_not_alevin`, `alevin-fry_splici_full_salign`, method = "spearman"),
    splici_sketch_full_cor = cor(`cellranger_cDNA_cellranger_not_alevin`, `alevin-fry_splici_full_sketch`, method = "spearman"),
    splici_salign_cr_cor = cor(`cellranger_cDNA_cellranger_not_alevin`, `alevin-fry_splici_cr_salign`, method = "spearman"),
    splici_sketch_cr_cor = cor(`cellranger_cDNA_cellranger_not_alevin`, `alevin-fry_splici_cr_sketch`, method = "spearman")
  )
nucleus_qc_common_cor %>% 
  dplyr::group_by(sample) %>%
  dplyr::summarize(
    splici_salign_full_cor = cor(`cellranger_splici_cellranger_not_alevin`, `alevin-fry_splici_full_salign`, method = "spearman"),
    splici_sketch_full_cor = cor(`cellranger_splici_cellranger_not_alevin`, `alevin-fry_splici_full_sketch`, method = "spearman"),
    splici_salign_cr_cor = cor(`cellranger_splici_cellranger_not_alevin`, `alevin-fry_splici_cr_salign`, method = "spearman"),
    splici_sketch_cr_cor = cor(`cellranger_splici_cellranger_not_alevin`, `alevin-fry_splici_cr_sketch`, method = "spearman")
  )

We have previously looked at sample 118 and 119 and they have very few cells that are identified, which is why we added in sample 220 and 221. Here, I am looking at 220 and 221 which both have > 5000 cells making them better candidates for these comparisons. All of these tools have very high correlations with cellranger for both the single-cell and single-nucleus samples. Below, I have made a few plots showing the direct comparisons for all of the variations using the --sketch option in comparison to cellranger and we see high correlations across the board.

ggplot(cell_qc_common_cor, aes(x = `cellranger_cDNA_cellranger_not_alevin`, y = `alevin-fry_cDNA_full_sketch`)) +
  geom_point(size = 0.5, alpha = 0.1) + 
  facet_wrap(~ sample) + 
  scale_x_log10() + 
  scale_y_log10() + 
  labs(x = "Cell Ranger UMI/cell", y = "Alevin Fry, cDNA index, Full Resolution Sketch UMI/cell") + 
  theme_classic()

ggplot(cell_qc_common_cor, aes(x = `cellranger_cDNA_cellranger_not_alevin`, y = `alevin-fry_cDNA_cr_sketch`)) +
  geom_point(size = 0.5, alpha = 0.1) + 
  facet_wrap(~ sample) + 
  scale_x_log10() + 
  scale_y_log10() + 
  labs(x = "Cell Ranger UMI/cell", y = "Alevin Fry, cDNA index, cr-like Resolution Sketch UMI/cell") + 
  theme_classic()

ggplot(cell_qc_common_cor, aes(x = `cellranger_cDNA_cellranger_not_alevin`, y = `alevin-fry_splici_full_sketch`)) +
  geom_point(size = 0.5, alpha = 0.1) + 
  facet_wrap(~ sample) + 
  scale_x_log10() + 
  scale_y_log10() + 
  labs(x = "Cell Ranger UMI/cell", y = "Alevin Fry, splici index, Full Resolution Sketch UMI/cell") + 
  theme_classic()

ggplot(cell_qc_common_cor, aes(x = `cellranger_cDNA_cellranger_not_alevin`, y = `alevin-fry_splici_cr_sketch`)) +
  geom_point(size = 0.5, alpha = 0.1) + 
  facet_wrap(~ sample) + 
  scale_x_log10() + 
  scale_y_log10() + 
  labs(x = "Cell Ranger UMI/cell", y = "Alevin Fry, splici index, cr-like Resolution Sketch UMI/cell") + 
  theme_classic()

ggplot(nucleus_qc_common_cor, aes(x = `cellranger_splici_cellranger_not_alevin`, y = `alevin-fry_splici_full_sketch`)) +
  geom_point(size = 0.5, alpha = 0.1) + 
  facet_wrap(~ sample) + 
  scale_x_log10() + 
  scale_y_log10() + 
  labs(x = "Cell Ranger UMI/cell", y = "Alevin Fry, splici index, Full Resolution Sketch UMI/cell") + 
  theme_classic()

ggplot(nucleus_qc_common_cor, aes(x = `cellranger_splici_cellranger_not_alevin`, y = `alevin-fry_splici_cr_sketch`)) +
  geom_point(size = 0.5, alpha = 0.1) + 
  facet_wrap(~ sample) + 
  scale_x_log10() + 
  scale_y_log10() + 
  labs(x = "Cell Ranger UMI/cell", y = "Alevin Fry, splici index, cr-like Resolution Sketch UMI/cell") + 
  theme_classic()

Per Gene QC Metrics

Next, we will look at some metrics comparing mean gene expression across genes identified for each sample using each tool. To do that, we will first filter by only those genes that are detected in more than 5% of cells and shared across all tool configurations.

# combine rowdata with quant info
rowdata_info_df <- rowdata_df %>%
  dplyr::mutate(tool = dplyr::case_when(tool == "alevin-fry-unfiltered" ~ "alevin-fry",
                                        tool == "cellranger" ~ "cellranger")) %>%
  dplyr::left_join(quant_info,
                   by = c("tool" = "tool", 
                          "quant_id" = "quant_dir"))


gene_counts <- rowdata_info_df %>% 
  # remove genes that have a low frequency of being detected
  dplyr::filter(detected >= 5.0) %>%
  dplyr::count(gene_id, sample)

common_genes <- gene_counts %>%
  dplyr::filter(n == 9) %>%
  dplyr::pull(gene_id)

rowdata_qc_common <- rowdata_info_df %>%
  dplyr::filter(
    (gene_id %in% common_genes) 
  )
# split into cell and nucleus
cell_rowdata_common <- rowdata_qc_common %>%
  dplyr::filter(seq_unit == "cell")
nucleus_rowdata_common <- rowdata_qc_common %>%
  dplyr::filter(seq_unit == "nucleus" & index_type == "splici")
# spread table for comparisons
cell_rowdata_cor <- cell_rowdata_common %>%
  dplyr::select(tool, index_type, alevin_resolution, alevin_alignment, gene_id, sample, mean) %>%
  # spread the mean expression stats to one column per caller
  tidyr::pivot_wider(id_cols = c(gene_id, sample),
                     names_from = c("tool", "index_type", "alevin_resolution", "alevin_alignment"),
                     values_from = mean) %>%
  # drop rows with NA values to ease correlation calculations
  tidyr::drop_na()

nucleus_rowdata_cor <- nucleus_rowdata_common %>%
  dplyr::select(tool, index_type, alevin_resolution, alevin_alignment, gene_id, sample, mean) %>%
  tidyr::pivot_wider(id_cols = c(gene_id, sample),
                     names_from = c("tool", "index_type", "alevin_resolution", "alevin_alignment"),
                     values_from = mean) %>%
  tidyr::drop_na()

Now we can look at the correlation of mean gene expression across each of our tool configurations in each sample.

cell_rowdata_cor %>% 
  dplyr::group_by(sample) %>%
  dplyr::summarize(
    cDNA_salign_full_cor = cor(`cellranger_cDNA_not_alevin_not_alevin`, `alevin-fry_cDNA_full_salign`, method = "spearman"),
    cDNA_sketch_full_cor =cor(`cellranger_cDNA_not_alevin_not_alevin`, `alevin-fry_cDNA_full_sketch`, method = "spearman"),
    cDNA_salign_cr_cor = cor(`cellranger_cDNA_not_alevin_not_alevin`, `alevin-fry_cDNA_cr_salign`, method = "spearman"),
    cDNA_sketch_cr_cor = cor(`cellranger_cDNA_not_alevin_not_alevin`, `alevin-fry_cDNA_cr_sketch`, method = "spearman"), 
    splici_salign_full_cor = cor(`cellranger_cDNA_not_alevin_not_alevin`, `alevin-fry_splici_full_salign`, method = "spearman"),
    splici_sketch_full_cor = cor(`cellranger_cDNA_not_alevin_not_alevin`, `alevin-fry_splici_full_sketch`, method = "spearman"),
    splici_salign_cr_cor = cor(`cellranger_cDNA_not_alevin_not_alevin`, `alevin-fry_splici_cr_salign`, method = "spearman"),
    splici_sketch_cr_cor = cor(`cellranger_cDNA_not_alevin_not_alevin`, `alevin-fry_splici_cr_sketch`, method = "spearman")
  )
nucleus_rowdata_cor %>% 
  dplyr::group_by(sample) %>%
  dplyr::summarize(
    splici_salign_full_cor = cor(`cellranger_splici_not_alevin_not_alevin`, `alevin-fry_splici_full_salign`, method = "spearman"),
    splici_sketch_full_cor = cor(`cellranger_splici_not_alevin_not_alevin`, `alevin-fry_splici_full_sketch`, method = "spearman"),
    splici_salign_cr_cor = cor(`cellranger_splici_not_alevin_not_alevin`, `alevin-fry_splici_cr_salign`, method = "spearman"),
    splici_sketch_cr_cor = cor(`cellranger_splici_not_alevin_not_alevin`, `alevin-fry_splici_cr_sketch`, method = "spearman")
  )

Just like with UMIs/cell, these correlations are quite high across all of the tools and all of the samples. The biggest drop in correlation does seem to be in the splici_salign_full and splici_sketch_full vs. cellranger comparisons in the single-cell samples with only around 0.95-0.97 in correlation coefficients.

We can look at a few examples more closely to see how well the mean gene expression for each gene actually lines up.

ggplot(cell_rowdata_cor, aes(x = `cellranger_cDNA_not_alevin_not_alevin`, y = `alevin-fry_cDNA_full_sketch`)) +
  geom_point(size = 0.5, alpha = 0.1) + 
  facet_wrap(~ sample) + 
  scale_x_log10() + 
  scale_y_log10() + 
  labs(x = "Cell Ranger mean gene expression", y = "Alevin Fry, cDNA index, Full Resolution Sketch Mean gene expression") + 
  theme_classic()

ggplot(cell_rowdata_cor, aes(x = `cellranger_cDNA_not_alevin_not_alevin`, y = `alevin-fry_cDNA_cr_sketch`)) +
  geom_point(size = 0.5, alpha = 0.1) + 
  facet_wrap(~ sample) + 
  scale_x_log10() + 
  scale_y_log10() + 
  labs(x = "Cell Ranger mean gene expression", y = "Alevin Fry, cDNA index, cr-like Resolution Sketch Mean gene expression") + 
  theme_classic()

ggplot(cell_rowdata_cor, aes(x = `cellranger_cDNA_not_alevin_not_alevin`, y = `alevin-fry_cDNA_cr_salign`)) +
  geom_point(size = 0.5, alpha = 0.1) + 
  facet_wrap(~ sample) + 
  scale_x_log10() + 
  scale_y_log10() + 
  labs(x = "Cell Ranger mean gene expression", y = "Alevin Fry, cDNA index, cr-like Resolution salign Mean gene expression") + 
  theme_classic()

ggplot(cell_rowdata_cor, aes(x = `cellranger_cDNA_not_alevin_not_alevin`, y = `alevin-fry_splici_full_sketch`)) +
  geom_point(size = 0.5, alpha = 0.1) + 
  facet_wrap(~ sample) + 
  scale_x_log10() + 
  scale_y_log10() + 
  labs(x = "Cell Ranger mean gene expression", y = "Alevin Fry, splici index, Full Resolution Sketch Mean gene expression") + 
  theme_classic()

ggplot(cell_rowdata_cor, aes(x = `cellranger_cDNA_not_alevin_not_alevin`, y = `alevin-fry_splici_cr_sketch`)) +
  geom_point(size = 0.5, alpha = 0.1) + 
  facet_wrap(~ sample) + 
  scale_x_log10() + 
  scale_y_log10() + 
  labs(x = "Cell Ranger mean gene expression", y = "Alevin Fry, splici index, cr-like Resolution Sketch Mean gene expression") + 
  theme_classic()

ggplot(cell_rowdata_cor, aes(x = `cellranger_cDNA_not_alevin_not_alevin`, y = `alevin-fry_splici_cr_salign`)) +
  geom_point(size = 0.5, alpha = 0.1) + 
  facet_wrap(~ sample) + 
  scale_x_log10() + 
  scale_y_log10() + 
  labs(x = "Cell Ranger mean gene expression", y = "Alevin Fry, splici index, cr-like Resolution Salign Mean gene expression") + 
  theme_classic()

Interestingly, when you look at the cDNA index, you do see genes with higher expression found in Alevin-fry in compared to cellranger (shown by a group to the upper left of the diagonal), but when you move to the splici index that group disappears. With the splici index and full resolution there still appears to be some increase in gene expression detected in Alevin-fry, but with the cr-like resolution it almost looks like there is now lower detection in gene expression and some genes have lost gene expression (shown with a group to the right of the diagonal in the last plot).

Additionally, when you look at the --sketch vs. the salign alignment, there appears to be some lower expressed genes that pop up off the diagonal with --sketch and the cDNA index, but not as much with the splici index when looking at the cr-like resolution.

Let’s look at the single-nuclei data.

ggplot(nucleus_rowdata_cor, aes(x = `cellranger_splici_not_alevin_not_alevin`, y = `alevin-fry_splici_full_sketch`)) +
  geom_point(size = 0.5, alpha = 0.1) + 
  facet_wrap(~ sample) + 
  scale_x_log10() + 
  scale_y_log10() + 
  labs(x = "Cell Ranger mean gene expression", y = "Alevin Fry, splici index, Full Resolution Sketch Mean gene expression") + 
  theme_classic()

ggplot(nucleus_rowdata_cor, aes(x = `cellranger_splici_not_alevin_not_alevin`, y = `alevin-fry_splici_full_salign`)) +
  geom_point(size = 0.5, alpha = 0.1) + 
  facet_wrap(~ sample) + 
  scale_x_log10() + 
  scale_y_log10() + 
  labs(x = "Cell Ranger mean gene expression", y = "Alevin Fry, splici index, Full Resolution Salign Mean gene expression") + 
  theme_classic()

ggplot(nucleus_rowdata_cor, aes(x = `cellranger_splici_not_alevin_not_alevin`, y = `alevin-fry_splici_cr_sketch`)) +
  geom_point(size = 0.5, alpha = 0.1) + 
  facet_wrap(~ sample) + 
  scale_x_log10() + 
  scale_y_log10() + 
  labs(x = "Cell Ranger mean gene expression", y = "Alevin Fry, splici index, cr-like Resolution Sketch Mean gene expression") + 
  theme_classic()

ggplot(nucleus_rowdata_cor, aes(x = `cellranger_splici_not_alevin_not_alevin`, y = `alevin-fry_splici_cr_salign`)) +
  geom_point(size = 0.5, alpha = 0.1) + 
  facet_wrap(~ sample) + 
  scale_x_log10() + 
  scale_y_log10() + 
  labs(x = "Cell Ranger mean gene expression", y = "Alevin Fry, splici index, cr-like Resolution Salign Mean gene expression") + 
  theme_classic()

Here, we see a similar trend where with the full resolution there are genes with increased expression in Alevin-fry, while with the cr-like it seems more centered around the diagonal with some genes having increased expression and some having lower expression than in cellranger. There also doesn’t seem to be any obvious changes between mean gene expression with --sketch and salign in the single-nuclei samples.

Some closing thoughts:

Should we be exploring some different filtering options with Alevin-fry? It looks like Alevin-fry is doing just as good of a job as cellranger, but specifically with the single-nuclei data, we see more cells with low counts that are in the final counts matrix - maybe this isn’t a problem and low count cells would get removed ideally before any downstream analysis anyways.

Splici with single-cell samples seems to be performing similarly to the other Alevin-fry modes and cellranger - it even looks like it does decrease some gene expression in genes that are poorly correlated between Alevin-fry and cellranger.

Cr-like gives similar results to cellranger, more so than the full resolution, although it does look like we could be leading to some lower gene expression in the single-cell samples with Alevin-fry. I’m not sure I can confidently make a decision here on which one would be the appropriate choice yet (although I can say I think both seem to do well).

LS0tCnRpdGxlOiAiQWxldmluLUZyeSB2cy4gQ2VsbHJhbmdlciBDb21wYXJpc29uIgphdXRob3I6ICJBbGx5IEhhd2tpbnMgZm9yIENDREwiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKSW4gdGhpcyBub3RlYm9vaywgd2Ugc3BlY2lmaWNhbGx5IGNvbXBhcmUgdXNpbmcgQWxldmluLWZyeSB3aXRoIGNlbGxyYW5nZXIgdG8gcXVhbnRpZnkgYm90aCBzaW5nbGUtY2VsbCBhbmQgc2luZ2xlLW51Y2xlaSBSTkEgc2VxIGRhdGEuIAoKRm9yIGNlbGxyYW5nZXIsIHdlIGFyZSB1c2luZyB0aGUgZGVmYXVsdCBwYXJhbWV0ZXJzIGZvciBzaW5nbGUtY2VsbCBSTkEtc2VxIGFuZCB0aGUgYC0taW5jbHVkZS1pbnRyb25zYCBvcHRpb24gZm9yIHRoZSBzaW5nbGUtbnVjbGVpIFJOQS1zZXEgb3B0aW9uLCB3aGljaCBoYXMgYmVlbiBsYWJlbGVkIGFzIHRoZSBgc3BsaWNpYCBpbmRleCBmb3IgZWFzeSBjb21wYXJpc29uIHdpdGggYWxldmluLWZyeS4gCkZvciBBbGV2aW4tZnJ5LCB3ZSBhcmUgaW50ZXJlc3RlZCBpbiBhIGZldyBkaWZmZXJlbnQgcGFyYW1ldGVycyBzcGVjaWZpY2FsbHk6IAoKLSBUaGUgdXNlIG9mIHRoZSBzcGxpY2kgaW5kZXggdnMuIHRoZSB0cmFuc2NyaXB0b21lIGluZGV4IG9ubHkgZm9yIHNpbmdsZS1jZWxsIFJOQS1zZXEgc2FtcGxlcyAKLSBVc2Ugb2YgdGhlIGNyLWxpa2Ugb3IgZnVsbCByZXNvbHV0aW9uIAotIFVzZSBvZiBwc2V1ZG9hbGlnbm1lbnQgKHNrZXRjaCkgb3Igc2VsZWN0aXZlIGFsaWdubWVudCAoc2FsaWduKQoKUHJldmlvdXNseSwgd2UgaGFkIGZvdW5kIHRoYXQgdGhlIHNrZXRjaCBwZXJmb3JtZWQgd2VsbCwgYWx0aG91Z2ggdGhlcmUgd2FzIGEgc2xpZ2h0IGluY3JlYXNlIGluIFVNSXMvY2VsbCBhbmQgZ2VuZXMgZGV0ZWN0ZWQvY2VsbC4gCkRhdGEgaGFzIGFsc28gc3VyZmFjZWQgZnJvbSBEb2JpbiBfZXQgYWwuXyBpbiBbU1RBUnNvbG86IGFjY3VyYXRlLCBmYXN0IGFuZCB2ZXJzYXRpbGUgbWFwcGluZy9xdWFudGlmaWNhdGlvbiBvZiBzaW5nbGUtY2VsbCBhbmQgc2luZ2xlLW51Y2xldXMgUk5BLXNlcSBkYXRhXShodHRwczovL3d3dy5iaW9yeGl2Lm9yZy9jb250ZW50LzEwLjExMDEvMjAyMS4wNS4wNS40NDI3NTV2MSkgYW5kIGluIGFuIFthbGV2aW4tZnJ5IHR1dG9yaWFsXShodHRwczovL2NvbWJpbmUtbGFiLmdpdGh1Yi5pby9hbGV2aW4tZnJ5LXR1dG9yaWFscy8yMDIxL2ltcHJvdmluZy10eG9tZS1zcGVjaWZpY2l0eS8pIGluZGljYXRpbmcgcHNldWRvYWxpZ25lcnMgaGF2ZSBhIHRlbmRlbmN5IHRvIHJlc3VsdCBpbiBmYWxzZSBkZXRlY3Rpb24gb2YgaW5jcmVhc2VkIGdlbmUgZXhwcmVzc2lvbi4gClVzZSBvZiB0aGUgYHNwbGljaWAgaW5kZXggd2l0aCBhbGV2aW4tZnJ5IGhhcyBiZWVuIHJlcG9ydGVkIHRvIGRlY3JlYXNlIHRoaXMgZmFsc2UgcG9zaXRpdmUgZXhwcmVzc2lvbi4gCgpNb3JlIGFib3V0IHVzZSBvZiB0aGUgYHNwbGljaWAgaW5kZXggYW5kIGRpZmZlcmVudCByZXNvbHV0aW9ucyBjYW4gYmUgZm91bmQgaW4gdGhlIHByZS1wcmludCBvbiBbQWxldmluLWZyeS5dKGh0dHBzOi8vd3d3LmJpb3J4aXYub3JnL2NvbnRlbnQvMTAuMTEwMS8yMDIxLjA2LjI5LjQ1MDM3N3YxKQoKV2Ugd2lsbCBiZSB0ZXN0aW5nIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9ucyBvZiBhbGV2aW4tZnJ5OiAKCi0gc3BsaWNlZCAoY0ROQSkgdHhvbWUsIHNhbGlnbiwgZnVsbAotIHNwbGljZWQgKGNETkEpIHR4b21lLCBza2V0Y2gsIGZ1bGwKLSBzcGxpY2VkIChjRE5BKSB0eG9tZSwgc2FsaWduLCBjci1saWtlCi0gc3BsaWNlZCAoY0ROQSkgdHhvbWUsIHNrZXRjaCwgY3ItbGlrZQotIHVuc3BsaWNlZCAoc3BsaWNpKSB0eG9tZSwgc2FsaWduLCBmdWxsCi0gdW5zcGxpY2VkIChzcGxpY2kpIHR4b21lLCBza2V0Y2gsIGZ1bGwKLSB1bnNwbGljZWQgKHNwbGljaSkgdHhvbWUsIHNhbGlnbiwgY3ItbGlrZQotIHVuc3BsaWNlZCAoc3BsaWNpKSB0eG9tZSwgc2tldGNoLCBjci1saWtlCgpUaGVyZSBhcmUgdGhyZWUgc2luZ2xlLWNlbGwgc2FtcGxlcyAoU0NQQ1IwMDAwMDYsIFNDUENSMDAwMTI2LCBTQ1BDUjAwMDEyNykgYW5kIGZvdXIgc2luZ2xlLW51Y2xlaSBzYW1wbGVzIHRoYXQgd2VyZSB1c2VkIGZvciBjb21wYXJpc29ucyAoU0NQQ1IwMDAxMTgsIFNDUENSMDAwMTE5LCBTQ1BDUjAwMDIyMCwgU0NQQ1IwMDAyMjEpLiAKCiMjIFNldHVwIAoKYGBge3J9CmxpYnJhcnkobWFncml0dHIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShTaW5nbGVDZWxsRXhwZXJpbWVudCkKYGBgCgoKYGBge3J9CiMgbG9hZCBpbiBiZW5jaG1hcmtpbmcgZnVuY3Rpb25zIApmdW5jdGlvbl9wYXRoIDwtIGZpbGUucGF0aCgiLi4iLCAiYmVuY2htYXJraW5nLWZ1bmN0aW9ucyIsICJSIikKZmlsZS5wYXRoKGZ1bmN0aW9uX3BhdGgsIGxpc3QuZmlsZXMoZnVuY3Rpb25fcGF0aCwgcGF0dGVybiA9ICIuUiQiKSkgJT4lCiAgcHVycnI6OndhbGsoc291cmNlKQpgYGAKCgpgYGB7cn0KIyBwYXRoIHRvIHJlc3VsdHMgZmlsZXMgd2l0aCBzY2VzIGFuZCBxYyBkYXRhZnJhbWVzIApiYXNlX2RpciA8LSBoZXJlOjpoZXJlKCkKZmlsZV9kaXIgPC0gZmlsZS5wYXRoKGJhc2VfZGlyLCAicmVzdWx0cyIpCgojIHNjZSBmaWxlcyAKY0ROQV9zYWxpZ25fZnVsbF9maWxlIDwtIGZpbGUucGF0aChmaWxlX2RpciwgImNETkFfc2FsaWduX2Z1bGxfc2Nlcy5yZHMiKQpjRE5BX3NrZXRjaF9mdWxsX2ZpbGUgPC0gZmlsZS5wYXRoKGZpbGVfZGlyLCAiY0ROQV9za2V0Y2hfZnVsbF9zY2VzLnJkcyIpCmNETkFfc2FsaWduX2NyX2ZpbGUgPC0gZmlsZS5wYXRoKGZpbGVfZGlyLCAiY0ROQV9zYWxpZ25fY3Jfc2Nlcy5yZHMiKQpjRE5BX3NrZXRjaF9jcl9maWxlIDwtIGZpbGUucGF0aChmaWxlX2RpciwgImNETkFfc2tldGNoX2NyX3NjZXMucmRzIikKc3BsaWNpX3NhbGlnbl9mdWxsX2ZpbGUgPC0gZmlsZS5wYXRoKGZpbGVfZGlyLCAic3BsaWNpX3NhbGlnbl9mdWxsX3NjZXMucmRzIikKc3BsaWNpX3NrZXRjaF9mdWxsX2ZpbGUgPC0gZmlsZS5wYXRoKGZpbGVfZGlyLCAic3BsaWNpX3NrZXRjaF9mdWxsX3NjZXMucmRzIikKc3BsaWNpX3NhbGlnbl9jcl9maWxlIDwtIGZpbGUucGF0aChmaWxlX2RpciwgInNwbGljaV9zYWxpZ25fY3Jfc2Nlcy5yZHMiKQpzcGxpY2lfc2tldGNoX2NyX2ZpbGUgPC0gZmlsZS5wYXRoKGZpbGVfZGlyLCAic3BsaWNpX3NrZXRjaF9jcl9zY2VzLnJkcyIpCmNlbGxyYW5nZXJfZmlsZSA8LSBmaWxlLnBhdGgoZmlsZV9kaXIsICJjZWxscmFuZ2VyX3NjZXMucmRzIikKCiMgcWMgZmlsZXMgCgpxdWFudF9pbmZvX2ZpbGUgPC0gZmlsZS5wYXRoKGZpbGVfZGlyLCAicXVhbnRfaW5mby50c3YiKQpjb2xkYXRhX2RmX2ZpbGUgPC0gZmlsZS5wYXRoKGZpbGVfZGlyLCAiY29sZGF0YV9xYy50c3YiKQpyb3dkYXRhX2RmX2ZpbGUgPC0gZmlsZS5wYXRoKGZpbGVfZGlyLCAicm93ZGF0YV9xYy50c3YiKQoKIyBtaXRvIGdlbmUgbGlzdCAKbWl0b19maWxlIDwtIGZpbGUucGF0aChiYXNlX2RpciwgInNhbXBsZS1pbmZvIiwgIkhvbW9fc2FwaWVucy5HUkNoMzguMTAzLm1pdG9nZW5lcy50eHQiKQpgYGAKCgpgYGB7cn0KIyByZWFkIGluIHNjZXMKY0ROQV9zYWxpZ25fZnVsbCA8LSByZWFkcjo6cmVhZF9yZHMoY0ROQV9zYWxpZ25fZnVsbF9maWxlKQpjRE5BX3NrZXRjaF9mdWxsIDwtIHJlYWRyOjpyZWFkX3JkcyhjRE5BX3NrZXRjaF9mdWxsX2ZpbGUpCmNETkFfc2FsaWduX2NyIDwtIHJlYWRyOjpyZWFkX3JkcyhjRE5BX3NhbGlnbl9jcl9maWxlKQpjRE5BX3NrZXRjaF9jciA8LSByZWFkcjo6cmVhZF9yZHMoY0ROQV9za2V0Y2hfY3JfZmlsZSkKc3BsaWNpX3NhbGlnbl9mdWxsIDwtIHJlYWRyOjpyZWFkX3JkcyhzcGxpY2lfc2FsaWduX2Z1bGxfZmlsZSkKc3BsaWNpX3NrZXRjaF9mdWxsIDwtIHJlYWRyOjpyZWFkX3JkcyhzcGxpY2lfc2tldGNoX2Z1bGxfZmlsZSkKc3BsaWNpX3NhbGlnbl9jciA8LSByZWFkcjo6cmVhZF9yZHMoc3BsaWNpX3NhbGlnbl9jcl9maWxlKQpzcGxpY2lfc2tldGNoX2NyIDwtIHJlYWRyOjpyZWFkX3JkcyhzcGxpY2lfc2tldGNoX2NyX2ZpbGUpCmNlbGxyYW5nZXIgPC0gcmVhZHI6OnJlYWRfcmRzKGNlbGxyYW5nZXJfZmlsZSkKCiMgbWFrZSBhIGxpc3QgdGhhdCB3aWxsIGJlIHVzZWQgbGF0ZXIgZm9yIGNhbGN1bGF0aW5nIHFjIHdpdGggYSBzcGVjaWZpYyB0aHJlc2hvbGQKc2NlX2xpc3QgPC0gbGlzdCgKICBjRE5BX3NhbGlnbl9mdWxsID0gY0ROQV9zYWxpZ25fZnVsbCwKICBjRE5BX3NrZXRjaF9mdWxsID0gY0ROQV9za2V0Y2hfZnVsbCwKICBjRE5BX3NhbGlnbl9jciA9IGNETkFfc2FsaWduX2NyLAogIGNETkFfc2tldGNoX2NyID0gY0ROQV9za2V0Y2hfY3IsCiAgc3BsaWNpX3NhbGlnbl9mdWxsID0gc3BsaWNpX3NhbGlnbl9mdWxsLAogIHNwbGljaV9za2V0Y2hfZnVsbCA9IHNwbGljaV9za2V0Y2hfZnVsbCwKICBzcGxpY2lfc2FsaWduX2NyID0gc3BsaWNpX3NhbGlnbl9jciwKICBzcGxpY2lfc2tldGNoX2NyID0gc3BsaWNpX3NrZXRjaF9jciwKICBjZWxscmFuZ2VyID0gY2VsbHJhbmdlcgopCmBgYAoKCmBgYHtyfQojIHJlYWQgaW4gZGF0YWZyYW1lcyBuZWVkZWQgZm9yIHBsb3R0aW5nCnF1YW50X2luZm8gPC0gcmVhZHI6OnJlYWRfdHN2KHF1YW50X2luZm9fZmlsZSkKY29sZGF0YV9kZiA8LSByZWFkcjo6cmVhZF90c3YoY29sZGF0YV9kZl9maWxlKQpyb3dkYXRhX2RmIDwtIHJlYWRyOjpyZWFkX3Rzdihyb3dkYXRhX2RmX2ZpbGUpCmBgYAoKCmBgYHtyfQojIGxvYWQgaW4gbWl0byBnZW5lcyBmaWxlIHVzZWQgbGF0ZXIgCm1pdG9fZ2VuZXMgPC0gcmVhZHI6OnJlYWRfdHN2KG1pdG9fZmlsZSwgY29sX25hbWVzID0gImdlbmVfaWQiKQptaXRvX2dlbmVzIDwtIG1pdG9fZ2VuZXMgJT4lCiAgZHBseXI6OnB1bGwoZ2VuZV9pZCkgJT4lCiAgdW5pcXVlKCkKYGBgCgpgYGB7cn0KIyBwcmVwIHRoZSBkYXRhZnJhbWVzIGZvciBwbG90dGluZwojIyBmaXggZXJyb3IgZm9yIDIyMCBhbmQgMjIxIHJ1biB3aXRoIGNlbGxyYW5nZXIgdGhhdCBzaG91bGQgaGF2ZSBpbmRleF90eXBlIGFzIHNwbGljaSAKcXVhbnRfaW5mb1t3aGljaChxdWFudF9pbmZvJHF1YW50X2RpciA9PSAiU0NQQ1IwMDAyMjAtY2RuYS1wcmVfbVJOQSIpLCJpbmRleF90eXBlIl0gPC0gInNwbGljaSIKcXVhbnRfaW5mb1t3aGljaChxdWFudF9pbmZvJHF1YW50X2RpciA9PSAiU0NQQ1IwMDAyMjEtY2RuYS1wcmVfbVJOQSIpLCJpbmRleF90eXBlIl0gPC0gInNwbGljaSIKCiMgbWVyZ2UgY29sZGF0YSBkZiB3aXRoIHF1YW50X2luZm8KY29sZGF0YV9pbmZvX2RmIDwtIGNvbGRhdGFfZGYgJT4lCiAgZHBseXI6Om11dGF0ZSh0b29sID0gZHBseXI6OmNhc2Vfd2hlbih0b29sID09ICJhbGV2aW4tZnJ5LXVuZmlsdGVyZWQiIH4gImFsZXZpbi1mcnkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9vbCA9PSAiY2VsbHJhbmdlciIgfiAiY2VsbHJhbmdlciIpKSAlPiUKICBkcGx5cjo6bGVmdF9qb2luKHF1YW50X2luZm8sCiAgICAgICAgICAgICAgICAgICBieSA9IGMoInRvb2wiID0gInRvb2wiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAicXVhbnRfaWQiID0gInF1YW50X2RpciIpKSAlPiUKICAjIHJlbmFtZSB0aGUgbm90X2FsZXZpbiB0byBjZWxscmFuZ2VyIGZvciBwbG90dGluZyBwdXJwb3NlcwogIGRwbHlyOjptdXRhdGUoYWxldmluX3Jlc29sdXRpb24gPSBpZmVsc2UoYWxldmluX3Jlc29sdXRpb24gPT0gIm5vdF9hbGV2aW4iLCAiY2VsbHJhbmdlciIsIGFsZXZpbl9yZXNvbHV0aW9uKSkKYGBgCgojIyBDb21wYXJpc29uIG9mIFFDIE1ldHJpY3MKCiMjIyBNaXRvY2hvbmRyaWFsIENvbnRlbnQKCkZpcnN0LCB3ZSB3aWxsIHN0YXJ0IGJ5IGp1c3QgbG9va2luZyBhdCBtaXRvY2hvbmRyaWFsIGNvbnRlbnQgYWNyb3NzIGVhY2ggb2YgdGhlIHRvb2xzLiAKCgpgYGB7ciBmaWcuaGVpZ2h0ID0gNSwgZmlnLndpZHRoPTV9CmdncGxvdChjb2xkYXRhX2luZm9fZGYsIGFlcyh4ID0gdG9vbCwgeSA9IHN1YnNldHNfbWl0b19wZXJjZW50LCBmaWxsID0gdG9vbCkpICsgCiAgZ2VvbV9ib3hwbG90KCkgKyAKICBmYWNldF9ncmlkKHNhbXBsZSB+IGluZGV4X3R5cGUpICsgCiAgdGhlbWVfY2xhc3NpYygpICsgCiAgeWxhYigiJSBNaXRvIC9DZWxsIikgKyAKICB0aGVtZShheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCmBgYApJdCBsb29rcyBsaWtlIGdlbmVyYWxseSBtaXRvY2hvbmRyaWFsIGNvbnRlbnQgaXMgdW5pZm9ybSBhbmQgbG93IGFjcm9zcyBhbGwgdG9vbHMgYW5kIGFsbCBzYW1wbGVzLCB3aGljaCBpcyBncmVhdC4gCgpCZWZvcmUgZG9pbmcgYW55bW9yZSBwbG90dGluZywgbGV0J3Mgc3BsaXQgb3VyIGRhdGFmcmFtZSBieSBzaW5nbGUtY2VsbCBhbmQgc2luZ2xlLW51Y2xldXMgUk5BLXNlcSBzYW1wbGVzLiAKCmBgYHtyfQpjZWxsX2NvbGRhdGFfcWMgPC0gY29sZGF0YV9pbmZvX2RmICU+JQogIGRwbHlyOjpmaWx0ZXIoc2VxX3VuaXQgPT0gImNlbGwiKQoKbnVjbGV1c19jb2xkYXRhX3FjIDwtIGNvbGRhdGFfaW5mb19kZiAlPiUKICBkcGx5cjo6ZmlsdGVyKHNlcV91bml0ID09ICJudWNsZXVzIikgJT4lCiAgZHBseXI6OmZpbHRlcihpbmRleF90eXBlID09ICJzcGxpY2kiKQpgYGAKCiMjIyBQZXIgQ2VsbCBRQyBNZXRyaWNzIGluIGFsbCBjZWxscwoKV2UgYXJlIGdvaW5nIHRvIGxvb2sgYXQgc29tZSBRQyBtZXRyaWNzIGF0IGEgcGVyIGNlbGwgbGV2ZWwuIApTcGVjaWZpY2FsbHkgd2Ugd2lsbCBsb29rIGF0IFVNSS9jZWxsIGFuZCBnZW5lcyBkZXRlY3RlZC9jZWxsLiAKCmBgYHtyIGZpZy5oZWlnaHQ9MiwgZmlnLndpZHRoPTV9CmdncGxvdChudWNsZXVzX2NvbGRhdGFfcWMsIGFlcyh4ID0gYWxldmluX3Jlc29sdXRpb24sIHkgPSBzdW0sIGZpbGwgPSBhbGV2aW5fYWxpZ25tZW50KSkgKyAKICBnZW9tX2JveHBsb3QoKSArIAogIGZhY2V0X2dyaWQofiBzYW1wbGUpICsKICB0aGVtZV9jbGFzc2ljKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArIAogIHlsYWIoIlVNSS9jZWxsIikgKyAKICB4bGFiKCIiKSArCiAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKDAsMzAwMDApKQpgYGAKCmBgYHtyIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTV9CmdncGxvdChjZWxsX2NvbGRhdGFfcWMsIGFlcyh4ID0gYWxldmluX3Jlc29sdXRpb24sIHkgPSBzdW0sIGZpbGwgPSBhbGV2aW5fYWxpZ25tZW50KSkgKyAKICBnZW9tX2JveHBsb3QoKSArIAogIGZhY2V0X2dyaWQoc2FtcGxlIH4gaW5kZXhfdHlwZSkgKwogIHRoZW1lX2NsYXNzaWMoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsgCiAgeWxhYigiVU1JL2NlbGwiKSArIAogIHhsYWIoIiIpICsgCiAgeWxpbShjKDAsNTAwMDApKQpgYGAKSW4gbG9va2luZyBhdCBVTUkvY2VsbCBiZXR3ZWVuIGNlbGwgYW5kIG51Y2xldXMgc2FtcGxlcywgaXQncyB2ZXJ5IGNsZWFyIHRvIG1lIHRoYXQgdGhlIHNpbmdsZS1udWNsZWkgc2FtcGxlcyBoYXZlIGEgYml0IG1vcmUgZmx1Y3R1YXRpb24gLSBib3RoIGFjcm9zcyBzYW1wbGVzIGFuZCBhY3Jvc3MgdG9vbHMuIApUaGUgc2luZ2xlLWNlbGwgc2FtcGxlcyB0ZW5kIHRvIGJlIGZhaXJseSB1bmlmb3JtIGFjcm9zcyBhbGwgdG9vbHMsIGFsdGhvdWdoIGl0IGxvb2tzIGxpa2UgdGhlIG1lZGlhbiBpbiAxMjYgYW5kIDEyNyBpcyBsb3dlciBpbiBhbGV2aW4tZnJ5IHRoYW4gaW4gY2VsbHJhbmdlci4gClRoaXMgaXMgbm90IHRoZSBjYXNlIGluIHRoZSBzaW5nbGUtbnVjbGVpIHNhbXBsZXMgd2hlcmUgd2Ugc2VlIHRoYXQgYWxldmluLWZyeSBkb2Vzbid0IHNlZW0gdG8gYmUgY2FwdHVyaW5nIGFzIG1hbnkgY291bnRzLCBzcGVjaWZpY2FsbHkgaW4gc2FtcGxlIDIyMC4gCgpgYGB7ciBmaWcuaGVpZ2h0PTIsIGZpZy53aWR0aD01fQpnZ3Bsb3QobnVjbGV1c19jb2xkYXRhX3FjLCBhZXMoeCA9IGFsZXZpbl9yZXNvbHV0aW9uLCB5ID0gZGV0ZWN0ZWQsIGZpbGwgPSBhbGV2aW5fYWxpZ25tZW50KSkgKyAKICBnZW9tX2JveHBsb3QoKSArIAogIGZhY2V0X2dyaWQofiBzYW1wbGUpICsKICB0aGVtZV9jbGFzc2ljKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArIAogIHlsYWIoIkdlbmVzIGRldGVjdGVkL2NlbGwiKSArIAogIHhsYWIoIiIpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMCwxNTAwMCkpCmBgYAoKSGVyZSwgd2Ugc2VlIGV2ZW4gbW9yZSB2YXJpYXRpb24gaW4gZ2VuZXMgZGV0ZWN0ZWQgcGVyIGNlbGwgaW4gdGhlIHNpbmdsZS1udWNsZWkgc2FtcGxlcyB3aXRoIGNlbGxyYW5nZXIgdGVuZGluZyB0byBoYXZlIG11Y2ggdGlnaHRlciBJUVIncyB0aGFuIGFsZXZpbi1mcnkuIApIb3dldmVyLCB3ZSBhcmUgc3RpbGwgbG9va2luZyBhdCBhbGwgY2VsbHMgYW5kIG5vdCBqdXN0IHNoYXJlZCBjZWxscyBvbmx5LCBzbyB0aGF0IGNvdWxkIGJlIHBhcnQgb2YgdGhlIGlzc3VlLiAKQWxldmluLWZyeSBjb3VsZCBiZSBkZXRlY3RpbmcgbW9yZSBjZWxscyB3aXRoIGxvd2VyIGNvdW50cyB0aGFuIGNlbGxyYW5nZXIuCmBgYHtyIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTV9CmdncGxvdChjZWxsX2NvbGRhdGFfcWMsIGFlcyh4ID0gYWxldmluX3Jlc29sdXRpb24sIHkgPSBkZXRlY3RlZCwgZmlsbCA9IGFsZXZpbl9hbGlnbm1lbnQpKSArIAogIGdlb21fYm94cGxvdCgpICsgCiAgZmFjZXRfZ3JpZChzYW1wbGUgfiBpbmRleF90eXBlKSArCiAgdGhlbWVfY2xhc3NpYygpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyAKICB5bGFiKCJHZW5lcyBkZXRlY3RlZC9jZWxsIikgKyAKICB4bGFiKCIiKSArIAogIHlsaW0oYygwLDEwMDAwKSkKYGBgCgoKIyMjIFBlciBDZWxsIFFDIE1ldHJpY3MgaW4gU2hhcmVkIENlbGxzIG9ubHksIE5vIE1pbmltdW0gR2VuZSBDb3ZlcmFnZQoKTm93IGxldCdzIGxvb2sgYXQgdGhlIHNhbWUgbWV0cmljcyBidXQgaW4gc2hhcmVkIGNlbGxzIG9ubHkgdG8gc2VlIGlmIGFueSBvZiB0aGVzZSBmbHVjdHVhdGlvbnMgYWNyb3NzIHRvb2xzIGFyZSBiZWNhdXNlIGRpZmZlcmVudCBjZWxscyBhcmUgYmVpbmcgaWRlbnRpZmllZC4gCgpgYGB7cn0KIyBmaWx0ZXIgZm9yIGNlbGxzIHRoYXQgYXJlIGZvdW5kIGluIGFsbCBjb25maWd1cmF0aW9ucyBvZiBhbGV2aW4gKyBjZWxscmFuZ2VyCmNlbGxfY291bnRzIDwtIGNlbGxfY29sZGF0YV9xYyAlPiUgIAogIGRwbHlyOjpjb3VudChjZWxsX2lkLCBzYW1wbGUpCgpjb21tb25fY2VsbHMgPC0gY2VsbF9jb3VudHMgJT4lCiAgZHBseXI6OmZpbHRlcihuID09IDkpICU+JQogIGRwbHlyOjpwdWxsKGNlbGxfaWQpCgpjZWxsX3FjX2NvbW1vbiA8LSBjZWxsX2NvbGRhdGFfcWMgJT4lCiAgZHBseXI6OmZpbHRlcihjZWxsX2lkICVpbiUgY29tbW9uX2NlbGxzKSAKYGBgCgpgYGB7cn0KIyBmaWx0ZXIgZm9yIGNlbGxzIHRoYXQgYXJlIGZvdW5kIGluIGFsbCBjb25maWd1cmF0aW9ucyBvZiBhbGV2aW4gKyBjZWxscmFuZ2VyCm51Y2xlaV9jb3VudHMgPC0gbnVjbGV1c19jb2xkYXRhX3FjICU+JQogIGRwbHlyOjpjb3VudChjZWxsX2lkLCBzYW1wbGUpCgpjb21tb25fbnVjbGVpIDwtIG51Y2xlaV9jb3VudHMgJT4lCiAgZHBseXI6OmZpbHRlcihuID09IDUpICU+JQogIGRwbHlyOjpwdWxsKGNlbGxfaWQpCgpudWNsZXVzX3FjX2NvbW1vbiA8LSBudWNsZXVzX2NvbGRhdGFfcWMgJT4lCiAgZHBseXI6OmZpbHRlcigKICAgIChjZWxsX2lkICVpbiUgY29tbW9uX251Y2xlaSkKICApCmBgYAoKRG9lcyBtaXRvY2hvbmRyaWFsIGNvbnRlbnQgY2hhbmdlIHdoZW4gd2Ugb25seSBsb29rIGF0IHNoYXJlZCBkYXRhPwpJIGV4cGVjdCBub3Qgc2luY2UgaXQgd2FzIGFscmVhZHkgcHJldHR5IHVuaWZvcm0uIAoKYGBge3IgZmlnLmhlaWdodCA9IDUsIGZpZy53aWR0aCA9MTB9CiMgbWl0byBjb21wYXJpc29uIGFjcm9zcyBzaGFyZWQgY2VsbHMgb25seSBvZiBhbGwgcnVucwojIG51Y2xldXMgc2FtcGxlcyBmaXJzdCAKZ2dwbG90KG51Y2xldXNfcWNfY29tbW9uLCBhZXMoeCA9IGFsZXZpbl9yZXNvbHV0aW9uLCB5ID0gc3Vic2V0c19taXRvX3BlcmNlbnQsIGZpbGwgPSB0b29sKSkgKyAKICBnZW9tX2JveHBsb3QoKSArIAogIGZhY2V0X2dyaWQofiBzYW1wbGUpICsgCiAgdGhlbWVfY2xhc3NpYygpICsgCiAgeWxhYigiJSBNaXRvIC9DZWxsIikgKyAKICB0aGVtZShheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKQoKIyBzaW5nbGUgY2VsbApnZ3Bsb3QoY2VsbF9xY19jb21tb24sIGFlcyh4ID0gYWxldmluX3Jlc29sdXRpb24sIHkgPSBzdWJzZXRzX21pdG9fcGVyY2VudCwgZmlsbCA9IHRvb2wpKSArIAogIGdlb21fYm94cGxvdCgpICsgCiAgZmFjZXRfZ3JpZChzYW1wbGUgfiBpbmRleF90eXBlKSArIAogIHRoZW1lX2NsYXNzaWMoKSArIAogIHlsYWIoIiUgTWl0byAvQ2VsbCIpICsgCiAgdGhlbWUoYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgClRoZSBhbnN3ZXIgaXMgbm8sIGl0IHNlZW1zIHRvIHN0aWxsIGJlIHF1aXRlIHNpbWlsYXIsIGFsdGhvdWdoIGhlcmUgSSBhbSBwbG90dGluZyBpdCBieSBicmVha2luZyBvdXQgdGhlIHNpbmdsZS1udWNsZWkgc2FtcGxlcyBhbmQgeW91IGNhbiBzZWUgdGhhdCBTQ1BDUjAwMDExOCBoYXMgaGlnaGVyIG1pdG8gY29udGVudCB0aGFuIHRoZSBvdGhlciBzYW1wbGVzIGFuZCBmbHVjdHVhdGVzIGFjcm9zcyB0b29scy4gCkluIHRoZSBwcmV2aW91cyBiZW5jaG1hcmtpbmcgSSB3YXMgd29ycmllZCBhYm91dCB0aGlzIHNhbXBsZSBub3QgYmVpbmcgaGlnaCBxdWFsaXR5IGFuZCBhbHRob3VnaCB0ZWNobmljYWxseSB0aGUgbWl0byBjb250ZW50IGlzIHN0aWxsIGJlbG93IDIwJSwgaXQgZG9lc24ndCBsb29rIGFzIHVuaWZvcm0gYXMgaW4gMjIwIG9yIDIyMS4gCgpMZXQncyBsb29rIGF0IFVNSS9jZWxsIGFuZCBnZW5lcyBkZXRlY3RlZC9jZWxsIGluIHRoZSBzaGFyZWQgY2VsbHMuIAoKYGBge3IgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9Mn0KZ2dwbG90KG51Y2xldXNfcWNfY29tbW9uLCBhZXMoeCA9IGFsZXZpbl9yZXNvbHV0aW9uLCB5ID0gc3VtLCBmaWxsID0gYWxldmluX2FsaWdubWVudCkpICsgCiAgZ2VvbV9ib3hwbG90KCkgKyAKICBmYWNldF9ncmlkKH4gc2FtcGxlKSArCiAgdGhlbWVfY2xhc3NpYygpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyAKICB5bGFiKCJVTUkvY2VsbCIpICsgCiAgeGxhYigiIikgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLDMwMDAwKSkKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD01fQpnZ3Bsb3QoY2VsbF9xY19jb21tb24sIGFlcyh4ID0gYWxldmluX3Jlc29sdXRpb24sIHkgPSBzdW0sIGZpbGwgPSBhbGV2aW5fYWxpZ25tZW50KSkgKyAKICBnZW9tX2JveHBsb3QoKSArIAogIGZhY2V0X2dyaWQoc2FtcGxlIH4gaW5kZXhfdHlwZSkgKwogIHRoZW1lX2NsYXNzaWMoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsgCiAgeWxhYigiVU1JL2NlbGwiKSArIAogIHhsYWIoIiIpICsgCiAgeWxpbShjKDAsNTAwMDApKQpgYGAKCmBgYHtyIGZpZy53aWR0aCA9IDUsIGZpZy5oZWlnaHQ9Mn0KZ2dwbG90KG51Y2xldXNfcWNfY29tbW9uLCBhZXMoeCA9IGFsZXZpbl9yZXNvbHV0aW9uLCB5ID0gZGV0ZWN0ZWQsIGZpbGwgPSBhbGV2aW5fYWxpZ25tZW50KSkgKyAKICBnZW9tX2JveHBsb3QoKSArIAogIGZhY2V0X2dyaWQofiBzYW1wbGUpICsKICB0aGVtZV9jbGFzc2ljKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArIAogIHlsYWIoIkdlbmVzIGRldGVjdGVkL2NlbGwiKSArIAogIHhsYWIoIiIpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMCwxNTAwMCkpCmBgYAoKYGBge3IgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9NX0KZ2dwbG90KGNlbGxfcWNfY29tbW9uLCBhZXMoeCA9IGFsZXZpbl9yZXNvbHV0aW9uLCB5ID0gZGV0ZWN0ZWQsIGZpbGwgPSBhbGV2aW5fYWxpZ25tZW50KSkgKyAKICBnZW9tX2JveHBsb3QoKSArIAogIGZhY2V0X2dyaWQoc2FtcGxlIH4gaW5kZXhfdHlwZSkgKwogIHRoZW1lX2NsYXNzaWMoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsgCiAgeWxhYigiR2VuZXMgZGV0ZWN0ZWQvY2VsbCIpICsgCiAgeGxhYigiIikgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLDEwMDAwKSkKYGBgCk92ZXJhbGwsIGl0IGxvb2tzIGxpa2Ugd2UgaGF2ZSBlbGltaW5hdGVkIG1hbnkgb2YgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gdGhlIHRvb2xzIGluIHRoZSBzaW5nbGUtbnVjbGVpIFJOQS1zZXEgZGF0YS4gCkFsdGhvdWdoIGl0IGFwcGVhcnMgdGhhdCBhY3Jvc3MgdGhlIGJvYXJkIHRoZSBjci1saWtlIHJlc29sdXRpb24gZ2l2ZXMgbW9yZSBvbiBwYXIgZGlzdHJpYnV0aW9ucyB0byBjZWxscmFuZ2VyIHRoYW4gdGhlIGZ1bGwgcmVzb2x1dGlvbi4gClRoZSBgZnVsbGAgcmVzb2x1dGlvbiBzZWVtcyB0byBoYXZlIGhpZ2hlciBVTUlzL2NlbGwgYW5kIGdlbmVzL2NlbGwgYWNyb3NzIG1vc3Qgc2FtcGxlcy4gCkFub3RoZXIgY29uc2lzdGVudCBmaW5kaW5nIGlzIHRoYXQgdGhlIGAtLXNrZXRjaGAgb3IgcHNldWRvYWxpZ25tZW50IGlzIGdpdmluZyBoaWdoZXIgVU1Jcy9jZWxsIGFuZCBnZW5lcyBkZXRlY3RlZC9jZWxsIGFjcm9zcyB0aGUgYm9hcmQsIGFsdGhvdWdoIHRoaXMgaXMgbW9yZSBub3RpY2VhYmxlIGluIHRoZSBzaW5nbGUtbnVjbGVpIHNhbXBsZXMuIApJdCBhbHNvIHNlZW1zIHRvIGJlIG1vcmUgYXBwYXJlbnQgd2l0aCB0aGUgYGZ1bGxgIHJlc29sdXRpb24gd2hpY2ggaXMgcHJvYmFibHkgdG8gYmUgZXhwZWN0ZWQgc2luY2UgdGhlIGBmdWxsYCByZXNvbHV0aW9uIGlzIG5vdCB0aHJvd2luZyBvdXQgbXVsdGktbWFwcGVkIGdlbmVzIGxpa2UgdGhlIGBjci1saWtlYCByZXNvbHV0aW9uIHdpbGwuICAKCkl0IGRvZXMgbG9vayBsaWtlIGFsZXZpbi1mcnkgaXMgZGV0ZWN0aW5nIG1vcmUgY2VsbHMgdGhhbiBjZWxscmFuZ2VyLCBzbyBwZXJoYXBzIHRoZXJlIGlzIHNvbWV0aGluZyB3ZSBjb3VsZCBiZSBkb2luZyBhdCB0aGUgZmlsdGVyaW5nIHN0YWdlIHRoYXQgY291bGQgaGVscCBpbXByb3ZlIHRoaXM/IAoKTGV0J3MgdGFrZSBhIHF1aWNrIGxvb2sgYXQgdGhlIG51bWJlciBvZiBjZWxscyBkZXRlY3RlZCBieSB0b29sIGp1c3QgdG8gYmUgc3VyZSB0aGF0IHRoaXMgaHlwb3RoZXNpcyBpcyBjb3JyZWN0LiAKYGBge3IgZmlnLmhlaWdodD0gMiwgZmlnLndpZHRoPTV9CmNlbGxfbnVtYmVycyA8LSBjb2xkYXRhX2luZm9fZGYgJT4lIAogIGRwbHlyOjpncm91cF9ieShxdWFudF9pZCwgdG9vbCwgc2FtcGxlKSAlPiUgCiAgI2RwbHlyOjpmaWx0ZXIoc2FtcGxlICE9ICJTQ1BDUjAwMDExOCIpICU+JQogIGRwbHlyOjp0YWxseSgpCgpnZ3Bsb3QoY2VsbF9udW1iZXJzLCBhZXMoeCA9IHNhbXBsZSwgeSA9IG4sIGNvbG9yID0gdG9vbCkpICsgCiAgZ2VvbV9wb2ludCgpICsKICB0aGVtZV9jbGFzc2ljKCkKYGBgCkFzIGV4cGVjdGVkLCBmb3IgYWxsIHNhbXBsZXMgKGV4Y2VwdCBTQ1BDUjAwMDAwNiksIGNlbGxyYW5nZXIgaXMgZGV0ZWN0aW5nIGZld2VyIGNlbGxzIHRoYW4gaW4gYWxldmluLWZyeS4gCkknbSBhbHNvIG5vdCBzdXJlIHdoYXQgaGFzIGhhcHBlbmVkIHdpdGggU0NQQ1IwMDAxMTgsIGJ1dCB3ZSBzYXcgdGhpcyBwcmV2aW91c2x5IGFuZCBpdCBhbHNvIHNob3dzIHN0cmFuZ2UgcGF0dGVybnMgaW4gbWl0b2Nob25kcmlhbCBjb250ZW50IGFuZCB2ZXJ5IGxvdyBjb3ZlcmFnZSBhY3Jvc3MgdGhlIGJvYXJkLgoKCiMjIFBlciBDZWxsIFFDIE1ldHJpY3MgaW4gU2hhcmVkIENlbGxzLCBNaW5pbXVtIEdlbmUgQ292ZXJhZ2UgCgpUbyBzZWUgaWYgdGhlIGluY3JlYXNlIGluIGdlbmVzIGRldGVjdGVkL2NlbGwgaW4gdGhlIGZ1bGwgcmVzb2x1dGlvbiBjb3VsZCBiZSBkdWUgdG8gbG93IGNvdmVyZWQgZ2VuZXMsIGxldCdzIGxvb2sgYXQgdGhlIG51bWJlciBvZiBnZW5lcyBkZXRlY3RlZCBpZiB3ZSB3ZXJlIHRvIHJlbW92ZSBnZW5lcyBvbmx5IGZvdW5kIGluIDwgNSUgb2YgY2VsbHMuIAoKCmBgYHtyfQojIHVzZSBhZGRQZXJDZWxsUUMgd2l0aCBnZW5lIGRldGVjdGlvbiB0aHJlc2hvbGQKIyBvbmx5IGdlbmVzIHdpdGggZGV0ZWN0aW9uIGluID4gNSUgb2YgY2VsbHMgd2lsbCBiZSBpbmNsdWRlZCBpbiB0aGUgUUMgY2FsY3VsYXRpb25zCnNjZV90aHJlc2hvbGRfbGlzdCA8LSBzY2VfbGlzdCAlPiUKICBwdXJycjo6bWFwKAogICAgfiBwdXJycjo6bWFwKC54LCBzY3BjYVRvb2xzOjphZGRfY2VsbF9taXRvX3FjLCBtaXRvID0gbWl0b19nZW5lcywgdGhyZXNob2xkID0gNSkKICApCmBgYAoKCmBgYHtyfQojIGNoYW5nZSBuYW1lcyBvZiB0aGUgc2NlIGxpc3QgdG8gYmUgdGhlIHRvb2wgdXNlZCBmaXJzdApuYW1lcyhzY2VfdGhyZXNob2xkX2xpc3QpIDwtIGMocmVwKCJhbGV2aW4tZnJ5IiwgOCksICJjZWxscmFuZ2VyIikKCiMgbWVyZ2UgYmFjayBpbnRvIGEgZGF0YWZyYW1lIGZvciBwbG90dGluZyAKY29sZGF0YV90aHJlc2hvbGQgPC0gcHVycnI6Om1hcF9kZigKICBzY2VfdGhyZXNob2xkX2xpc3QsCiAgfiBwdXJycjo6bWFwX2RmKC54LCBzY3BjYVRvb2xzOjpjb2xkYXRhX3RvX2RmLCAuaWQgPSAicXVhbnRfaWQiKSwgCiAgLmlkID0gInRvb2wiCikKCmBgYAoKYGBge3J9CiMgbWVyZ2UgbmV3IGNvbGRhdGEgYmFjayB3aXRoIHF1YW50X2luZm8KY29sZGF0YV9pbmZvX3RocmVzaG9sZF9kZiA8LSBjb2xkYXRhX3RocmVzaG9sZCAlPiUKICBkcGx5cjo6bGVmdF9qb2luKHF1YW50X2luZm8sCiAgICAgICAgICAgICAgICAgICBieSA9IGMoInRvb2wiID0gInRvb2wiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAicXVhbnRfaWQiID0gInF1YW50X2RpciIpKSAlPiUKICAjIHJlbmFtZSB0aGUgbm90X2FsZXZpbiB0byBjZWxscmFuZ2VyIGZvciBwbG90dGluZyBwdXJwb3NlcwogIGRwbHlyOjptdXRhdGUoYWxldmluX3Jlc29sdXRpb24gPSBpZmVsc2UoYWxldmluX3Jlc29sdXRpb24gPT0gIm5vdF9hbGV2aW4iLCAiY2VsbHJhbmdlciIsIGFsZXZpbl9yZXNvbHV0aW9uKSkKCiMgYnJlYWsgb3V0IGludG8gc2luZ2xlIGNlbGwgYW5kIHNpbmdsZSBudWNsZXVzIApjZWxsX2NvbGRhdGFfdGhyZXNob2xkX3FjIDwtIGNvbGRhdGFfaW5mb190aHJlc2hvbGRfZGYgJT4lCiAgZHBseXI6OmZpbHRlcihzZXFfdW5pdCA9PSAiY2VsbCIpCgpudWNsZXVzX2NvbGRhdGFfdGhyZXNob2xkX3FjIDwtIGNvbGRhdGFfaW5mb190aHJlc2hvbGRfZGYgJT4lCiAgZHBseXI6OmZpbHRlcihzZXFfdW5pdCA9PSAibnVjbGV1cyIgJiBpbmRleF90eXBlID09ICJzcGxpY2kiKQpgYGAKCmBgYHtyfQojIGxvb2sgZm9yIHNoYXJlZCBjZWxscyBvbmx5IApjZWxsX2NvdW50c190aHJlc2hvbGQgPC0gY2VsbF9jb2xkYXRhX3RocmVzaG9sZF9xYyAlPiUgIAogIGRwbHlyOjpjb3VudChjZWxsX2lkLCBzYW1wbGUpCgpjb21tb25fY2VsbHNfdGhyZXNob2xkIDwtIGNlbGxfY291bnRzX3RocmVzaG9sZCAlPiUKICBkcGx5cjo6ZmlsdGVyKG4gPT0gOSkgJT4lCiAgZHBseXI6OnB1bGwoY2VsbF9pZCkKCmNlbGxfcWNfY29tbW9uX3RocmVzaG9sZCA8LSBjZWxsX2NvbGRhdGFfdGhyZXNob2xkX3FjICU+JQogIGRwbHlyOjpmaWx0ZXIoCiAgICAoY2VsbF9pZCAlaW4lIGNvbW1vbl9jZWxsc190aHJlc2hvbGQpIAogICkKYGBgCgpgYGB7cn0KIyBsb29rIGZvciBzaGFyZWQgY2VsbHMgb25seSAKbnVjbGVpX2NvdW50c190aHJlc2hvbGQgPC0gbnVjbGV1c19jb2xkYXRhX3RocmVzaG9sZF9xYyAlPiUKICBkcGx5cjo6Y291bnQoY2VsbF9pZCwgc2FtcGxlKQoKY29tbW9uX251Y2xlaV90aHJlc2hvbGQgPC0gbnVjbGVpX2NvdW50c190aHJlc2hvbGQgJT4lCiAgZHBseXI6OmZpbHRlcihuID09IDUpICU+JQogIGRwbHlyOjpwdWxsKGNlbGxfaWQpCgpudWNsZXVzX3FjX2NvbW1vbl90aHJlc2hvbGQgPC0gbnVjbGV1c19jb2xkYXRhX3RocmVzaG9sZF9xYyAlPiUKICBkcGx5cjo6ZmlsdGVyKAogICAgKGNlbGxfaWQgJWluJSBjb21tb25fbnVjbGVpX3RocmVzaG9sZCkKICApCmBgYAoKCmBgYHtyIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTJ9CmdncGxvdChudWNsZXVzX3FjX2NvbW1vbl90aHJlc2hvbGQsIGFlcyh4ID0gYWxldmluX3Jlc29sdXRpb24sIHkgPSBkZXRlY3RlZCwgZmlsbCA9IGFsZXZpbl9hbGlnbm1lbnQpKSArIAogIGdlb21fYm94cGxvdCgpICsgCiAgZmFjZXRfZ3JpZCh+IHNhbXBsZSkgKwogIHRoZW1lX2NsYXNzaWMoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsgCiAgeWxhYigiR2VuZXMgZGV0ZWN0ZWQvY2VsbCIpICsgCiAgeGxhYigiIikgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygwLDE1MDAwKSkKYGBgCmBgYHtyIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTN9CmdncGxvdChjZWxsX3FjX2NvbW1vbl90aHJlc2hvbGQsIGFlcyh4ID0gYWxldmluX3Jlc29sdXRpb24sIHkgPSBkZXRlY3RlZCwgZmlsbCA9IGFsZXZpbl9hbGlnbm1lbnQpKSArIAogIGdlb21fYm94cGxvdCgpICsgCiAgZmFjZXRfZ3JpZChzYW1wbGUgfiBpbmRleF90eXBlKSArCiAgdGhlbWVfY2xhc3NpYygpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyAKICB5bGFiKCJHZW5lcyBkZXRlY3RlZC9jZWxsIikgKyAKICB4bGFiKCIiKSArCiAgY29vcmRfY2FydGVzaWFuKHlsaW0gPSBjKDAsMTAwMDApKQpgYGAKSXQgZG9lcyBub3QgYXBwZWFyIHRoYXQgdGhlIGluY3JlYXNlIGluIGdlbmVzIGRldGVjdGVkIG9ic2VydmVkIGluIHRoZSBmdWxsIHJlc29sdXRpb24gaXMgcmVzb2x2ZWQgYnkgcmVtb3ZpbmcgbG93bHkgY292ZXJlZCBnZW5lcy4gCgojIyMgUGVyIENlbGwgQ29ycmVsYXRpb25zIGFjcm9zcyB0b29scwoKVGhlIGxhc3QgY29tcGFyaXNvbiB3ZSB3aWxsIG1ha2UgYXQgdGhlIHBlciBjZWxsIGxldmVsIGlzIHRvIGxvb2sgYXQgdGhlIGNvcnJlbGF0aW9uIG9mIFVNSS9jZWxsIGFjcm9zcyB0b29scy4gCkhlcmUgd2Ugd2lsbCBjb21wYXJlIGVhY2ggb2YgdGhlIGFsZXZpbi1mcnkgY29uZmlndXJhdGlvbnMgdG8gY2VsbHJhbmdlciBhbmQgbG9vayBhdCB0aGUgY29ycmVsYXRpb24gY29lZmZpY2llbnQgYW5kIHBsb3Qgc29tZSBvZiB0aGUgaW5kaXZpZHVhbCB2YWx1ZXMgb2YgVU1JL2NlbGwgdG8gc2VlIGhvdyB3ZWxsIGNvcnJlbGF0ZWQgdGhlc2UgdG9vbHMgYXJlLiAKCgpgYGB7cn0KIyBzcHJlYWQgdGFibGUgZm9yIGNvbXBhcmlzb25zCmNlbGxfcWNfY29tbW9uX2NvciA8LSBjZWxsX3FjX2NvbW1vbiAlPiUKICBkcGx5cjo6c2VsZWN0KHRvb2wsIGluZGV4X3R5cGUsIGFsZXZpbl9yZXNvbHV0aW9uLCBhbGV2aW5fYWxpZ25tZW50LCBjZWxsX2lkLCBzYW1wbGUsIHN1bSkgJT4lCiAgIyBzcHJlYWQgdGhlIG1lYW4gZXhwcmVzc2lvbiBzdGF0cyB0byBvbmUgY29sdW1uIHBlciBjYWxsZXIKICB0aWR5cjo6cGl2b3Rfd2lkZXIoaWRfY29scyA9IGMoY2VsbF9pZCwgc2FtcGxlKSwKICAgICAgICAgICAgICAgICAgICAgbmFtZXNfZnJvbSA9IGMoInRvb2wiLCAiaW5kZXhfdHlwZSIsICJhbGV2aW5fcmVzb2x1dGlvbiIsICJhbGV2aW5fYWxpZ25tZW50IiksCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlc19mcm9tID0gc3VtKSAlPiUKICAjIGRyb3Agcm93cyB3aXRoIE5BIHZhbHVlcyB0byBlYXNlIGNvcnJlbGF0aW9uIGNhbGN1bGF0aW9ucwogIHRpZHlyOjpkcm9wX25hKCkKCm51Y2xldXNfcWNfY29tbW9uX2NvciA8LSBudWNsZXVzX3FjX2NvbW1vbiAlPiUKICBkcGx5cjo6c2VsZWN0KHRvb2wsIGluZGV4X3R5cGUsIGFsZXZpbl9yZXNvbHV0aW9uLCBhbGV2aW5fYWxpZ25tZW50LCBjZWxsX2lkLCBzYW1wbGUsIHN1bSkgJT4lCiAgdGlkeXI6OnBpdm90X3dpZGVyKGlkX2NvbHMgPSBjKGNlbGxfaWQsIHNhbXBsZSksCiAgICAgICAgICAgICAgICAgICAgIG5hbWVzX2Zyb20gPSBjKCJ0b29sIiwgImluZGV4X3R5cGUiLCAiYWxldmluX3Jlc29sdXRpb24iLCAiYWxldmluX2FsaWdubWVudCIpLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IHN1bSkgJT4lCiAgdGlkeXI6OmRyb3BfbmEoKQpgYGAKCgpgYGB7cn0KY2VsbF9xY19jb21tb25fY29yICU+JSAKICBkcGx5cjo6Z3JvdXBfYnkoc2FtcGxlKSAlPiUKICBkcGx5cjo6c3VtbWFyaXplKAogICAgY0ROQV9zYWxpZ25fZnVsbF9jb3IgPSBjb3IoYGNlbGxyYW5nZXJfY0ROQV9jZWxscmFuZ2VyX25vdF9hbGV2aW5gLCBgYWxldmluLWZyeV9jRE5BX2Z1bGxfc2FsaWduYCwgbWV0aG9kID0gInNwZWFybWFuIiksCiAgICBjRE5BX3NrZXRjaF9mdWxsX2NvciA9Y29yKGBjZWxscmFuZ2VyX2NETkFfY2VsbHJhbmdlcl9ub3RfYWxldmluYCwgYGFsZXZpbi1mcnlfY0ROQV9mdWxsX3NrZXRjaGAsIG1ldGhvZCA9ICJzcGVhcm1hbiIpLAogICAgY0ROQV9zYWxpZ25fY3JfY29yID0gY29yKGBjZWxscmFuZ2VyX2NETkFfY2VsbHJhbmdlcl9ub3RfYWxldmluYCwgYGFsZXZpbi1mcnlfY0ROQV9jcl9zYWxpZ25gLCBtZXRob2QgPSAic3BlYXJtYW4iKSwKICAgIGNETkFfc2tldGNoX2NyX2NvciA9IGNvcihgY2VsbHJhbmdlcl9jRE5BX2NlbGxyYW5nZXJfbm90X2FsZXZpbmAsIGBhbGV2aW4tZnJ5X2NETkFfY3Jfc2tldGNoYCwgbWV0aG9kID0gInNwZWFybWFuIiksIAogICAgc3BsaWNpX3NhbGlnbl9mdWxsX2NvciA9IGNvcihgY2VsbHJhbmdlcl9jRE5BX2NlbGxyYW5nZXJfbm90X2FsZXZpbmAsIGBhbGV2aW4tZnJ5X3NwbGljaV9mdWxsX3NhbGlnbmAsIG1ldGhvZCA9ICJzcGVhcm1hbiIpLAogICAgc3BsaWNpX3NrZXRjaF9mdWxsX2NvciA9IGNvcihgY2VsbHJhbmdlcl9jRE5BX2NlbGxyYW5nZXJfbm90X2FsZXZpbmAsIGBhbGV2aW4tZnJ5X3NwbGljaV9mdWxsX3NrZXRjaGAsIG1ldGhvZCA9ICJzcGVhcm1hbiIpLAogICAgc3BsaWNpX3NhbGlnbl9jcl9jb3IgPSBjb3IoYGNlbGxyYW5nZXJfY0ROQV9jZWxscmFuZ2VyX25vdF9hbGV2aW5gLCBgYWxldmluLWZyeV9zcGxpY2lfY3Jfc2FsaWduYCwgbWV0aG9kID0gInNwZWFybWFuIiksCiAgICBzcGxpY2lfc2tldGNoX2NyX2NvciA9IGNvcihgY2VsbHJhbmdlcl9jRE5BX2NlbGxyYW5nZXJfbm90X2FsZXZpbmAsIGBhbGV2aW4tZnJ5X3NwbGljaV9jcl9za2V0Y2hgLCBtZXRob2QgPSAic3BlYXJtYW4iKQogICkKYGBgCgpgYGB7cn0KbnVjbGV1c19xY19jb21tb25fY29yICU+JSAKICBkcGx5cjo6Z3JvdXBfYnkoc2FtcGxlKSAlPiUKICBkcGx5cjo6c3VtbWFyaXplKAogICAgc3BsaWNpX3NhbGlnbl9mdWxsX2NvciA9IGNvcihgY2VsbHJhbmdlcl9zcGxpY2lfY2VsbHJhbmdlcl9ub3RfYWxldmluYCwgYGFsZXZpbi1mcnlfc3BsaWNpX2Z1bGxfc2FsaWduYCwgbWV0aG9kID0gInNwZWFybWFuIiksCiAgICBzcGxpY2lfc2tldGNoX2Z1bGxfY29yID0gY29yKGBjZWxscmFuZ2VyX3NwbGljaV9jZWxscmFuZ2VyX25vdF9hbGV2aW5gLCBgYWxldmluLWZyeV9zcGxpY2lfZnVsbF9za2V0Y2hgLCBtZXRob2QgPSAic3BlYXJtYW4iKSwKICAgIHNwbGljaV9zYWxpZ25fY3JfY29yID0gY29yKGBjZWxscmFuZ2VyX3NwbGljaV9jZWxscmFuZ2VyX25vdF9hbGV2aW5gLCBgYWxldmluLWZyeV9zcGxpY2lfY3Jfc2FsaWduYCwgbWV0aG9kID0gInNwZWFybWFuIiksCiAgICBzcGxpY2lfc2tldGNoX2NyX2NvciA9IGNvcihgY2VsbHJhbmdlcl9zcGxpY2lfY2VsbHJhbmdlcl9ub3RfYWxldmluYCwgYGFsZXZpbi1mcnlfc3BsaWNpX2NyX3NrZXRjaGAsIG1ldGhvZCA9ICJzcGVhcm1hbiIpCiAgKQpgYGAKV2UgaGF2ZSBwcmV2aW91c2x5IGxvb2tlZCBhdCBzYW1wbGUgMTE4IGFuZCAxMTkgYW5kIHRoZXkgaGF2ZSB2ZXJ5IGZldyBjZWxscyB0aGF0IGFyZSBpZGVudGlmaWVkLCB3aGljaCBpcyB3aHkgd2UgYWRkZWQgaW4gc2FtcGxlIDIyMCBhbmQgMjIxLiAKSGVyZSwgSSBhbSBsb29raW5nIGF0IDIyMCBhbmQgMjIxIHdoaWNoIGJvdGggaGF2ZSA+IDUwMDAgY2VsbHMgbWFraW5nIHRoZW0gYmV0dGVyIGNhbmRpZGF0ZXMgZm9yIHRoZXNlIGNvbXBhcmlzb25zLiAKQWxsIG9mIHRoZXNlIHRvb2xzIGhhdmUgdmVyeSBoaWdoIGNvcnJlbGF0aW9ucyB3aXRoIGNlbGxyYW5nZXIgZm9yIGJvdGggdGhlIHNpbmdsZS1jZWxsIGFuZCBzaW5nbGUtbnVjbGV1cyBzYW1wbGVzLiAKQmVsb3csIEkgaGF2ZSBtYWRlIGEgZmV3IHBsb3RzIHNob3dpbmcgdGhlIGRpcmVjdCBjb21wYXJpc29ucyBmb3IgYWxsIG9mIHRoZSB2YXJpYXRpb25zIHVzaW5nIHRoZSBgLS1za2V0Y2hgIG9wdGlvbiBpbiBjb21wYXJpc29uIHRvIGNlbGxyYW5nZXIgYW5kIHdlIHNlZSBoaWdoIGNvcnJlbGF0aW9ucyBhY3Jvc3MgdGhlIGJvYXJkLiAKCmBgYHtyfQpnZ3Bsb3QoY2VsbF9xY19jb21tb25fY29yLCBhZXMoeCA9IGBjZWxscmFuZ2VyX2NETkFfY2VsbHJhbmdlcl9ub3RfYWxldmluYCwgeSA9IGBhbGV2aW4tZnJ5X2NETkFfZnVsbF9za2V0Y2hgKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDAuNSwgYWxwaGEgPSAwLjEpICsgCiAgZmFjZXRfd3JhcCh+IHNhbXBsZSkgKyAKICBzY2FsZV94X2xvZzEwKCkgKyAKICBzY2FsZV95X2xvZzEwKCkgKyAKICBsYWJzKHggPSAiQ2VsbCBSYW5nZXIgVU1JL2NlbGwiLCB5ID0gIkFsZXZpbiBGcnksIGNETkEgaW5kZXgsIEZ1bGwgUmVzb2x1dGlvbiBTa2V0Y2ggVU1JL2NlbGwiKSArIAogIHRoZW1lX2NsYXNzaWMoKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoY2VsbF9xY19jb21tb25fY29yLCBhZXMoeCA9IGBjZWxscmFuZ2VyX2NETkFfY2VsbHJhbmdlcl9ub3RfYWxldmluYCwgeSA9IGBhbGV2aW4tZnJ5X2NETkFfY3Jfc2tldGNoYCkpICsKICBnZW9tX3BvaW50KHNpemUgPSAwLjUsIGFscGhhID0gMC4xKSArIAogIGZhY2V0X3dyYXAofiBzYW1wbGUpICsgCiAgc2NhbGVfeF9sb2cxMCgpICsgCiAgc2NhbGVfeV9sb2cxMCgpICsgCiAgbGFicyh4ID0gIkNlbGwgUmFuZ2VyIFVNSS9jZWxsIiwgeSA9ICJBbGV2aW4gRnJ5LCBjRE5BIGluZGV4LCBjci1saWtlIFJlc29sdXRpb24gU2tldGNoIFVNSS9jZWxsIikgKyAKICB0aGVtZV9jbGFzc2ljKCkKYGBgCmBgYHtyfQpnZ3Bsb3QoY2VsbF9xY19jb21tb25fY29yLCBhZXMoeCA9IGBjZWxscmFuZ2VyX2NETkFfY2VsbHJhbmdlcl9ub3RfYWxldmluYCwgeSA9IGBhbGV2aW4tZnJ5X3NwbGljaV9mdWxsX3NrZXRjaGApKSArCiAgZ2VvbV9wb2ludChzaXplID0gMC41LCBhbHBoYSA9IDAuMSkgKyAKICBmYWNldF93cmFwKH4gc2FtcGxlKSArIAogIHNjYWxlX3hfbG9nMTAoKSArIAogIHNjYWxlX3lfbG9nMTAoKSArIAogIGxhYnMoeCA9ICJDZWxsIFJhbmdlciBVTUkvY2VsbCIsIHkgPSAiQWxldmluIEZyeSwgc3BsaWNpIGluZGV4LCBGdWxsIFJlc29sdXRpb24gU2tldGNoIFVNSS9jZWxsIikgKyAKICB0aGVtZV9jbGFzc2ljKCkKYGBgCmBgYHtyfQpnZ3Bsb3QoY2VsbF9xY19jb21tb25fY29yLCBhZXMoeCA9IGBjZWxscmFuZ2VyX2NETkFfY2VsbHJhbmdlcl9ub3RfYWxldmluYCwgeSA9IGBhbGV2aW4tZnJ5X3NwbGljaV9jcl9za2V0Y2hgKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDAuNSwgYWxwaGEgPSAwLjEpICsgCiAgZmFjZXRfd3JhcCh+IHNhbXBsZSkgKyAKICBzY2FsZV94X2xvZzEwKCkgKyAKICBzY2FsZV95X2xvZzEwKCkgKyAKICBsYWJzKHggPSAiQ2VsbCBSYW5nZXIgVU1JL2NlbGwiLCB5ID0gIkFsZXZpbiBGcnksIHNwbGljaSBpbmRleCwgY3ItbGlrZSBSZXNvbHV0aW9uIFNrZXRjaCBVTUkvY2VsbCIpICsgCiAgdGhlbWVfY2xhc3NpYygpCmBgYApgYGB7cn0KZ2dwbG90KG51Y2xldXNfcWNfY29tbW9uX2NvciwgYWVzKHggPSBgY2VsbHJhbmdlcl9zcGxpY2lfY2VsbHJhbmdlcl9ub3RfYWxldmluYCwgeSA9IGBhbGV2aW4tZnJ5X3NwbGljaV9mdWxsX3NrZXRjaGApKSArCiAgZ2VvbV9wb2ludChzaXplID0gMC41LCBhbHBoYSA9IDAuMSkgKyAKICBmYWNldF93cmFwKH4gc2FtcGxlKSArIAogIHNjYWxlX3hfbG9nMTAoKSArIAogIHNjYWxlX3lfbG9nMTAoKSArIAogIGxhYnMoeCA9ICJDZWxsIFJhbmdlciBVTUkvY2VsbCIsIHkgPSAiQWxldmluIEZyeSwgc3BsaWNpIGluZGV4LCBGdWxsIFJlc29sdXRpb24gU2tldGNoIFVNSS9jZWxsIikgKyAKICB0aGVtZV9jbGFzc2ljKCkKYGBgCgpgYGB7cn0KZ2dwbG90KG51Y2xldXNfcWNfY29tbW9uX2NvciwgYWVzKHggPSBgY2VsbHJhbmdlcl9zcGxpY2lfY2VsbHJhbmdlcl9ub3RfYWxldmluYCwgeSA9IGBhbGV2aW4tZnJ5X3NwbGljaV9jcl9za2V0Y2hgKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDAuNSwgYWxwaGEgPSAwLjEpICsgCiAgZmFjZXRfd3JhcCh+IHNhbXBsZSkgKyAKICBzY2FsZV94X2xvZzEwKCkgKyAKICBzY2FsZV95X2xvZzEwKCkgKyAKICBsYWJzKHggPSAiQ2VsbCBSYW5nZXIgVU1JL2NlbGwiLCB5ID0gIkFsZXZpbiBGcnksIHNwbGljaSBpbmRleCwgY3ItbGlrZSBSZXNvbHV0aW9uIFNrZXRjaCBVTUkvY2VsbCIpICsgCiAgdGhlbWVfY2xhc3NpYygpCmBgYAoKIyMgUGVyIEdlbmUgUUMgTWV0cmljcwoKTmV4dCwgd2Ugd2lsbCBsb29rIGF0IHNvbWUgbWV0cmljcyBjb21wYXJpbmcgbWVhbiBnZW5lIGV4cHJlc3Npb24gYWNyb3NzIGdlbmVzIGlkZW50aWZpZWQgZm9yIGVhY2ggc2FtcGxlIHVzaW5nIGVhY2ggdG9vbC4gClRvIGRvIHRoYXQsIHdlIHdpbGwgZmlyc3QgZmlsdGVyIGJ5IG9ubHkgdGhvc2UgZ2VuZXMgdGhhdCBhcmUgZGV0ZWN0ZWQgaW4gbW9yZSB0aGFuIDUlIG9mIGNlbGxzIGFuZCBzaGFyZWQgYWNyb3NzIGFsbCB0b29sIGNvbmZpZ3VyYXRpb25zLiAgCgpgYGB7cn0KIyBjb21iaW5lIHJvd2RhdGEgd2l0aCBxdWFudCBpbmZvCnJvd2RhdGFfaW5mb19kZiA8LSByb3dkYXRhX2RmICU+JQogIGRwbHlyOjptdXRhdGUodG9vbCA9IGRwbHlyOjpjYXNlX3doZW4odG9vbCA9PSAiYWxldmluLWZyeS11bmZpbHRlcmVkIiB+ICJhbGV2aW4tZnJ5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvb2wgPT0gImNlbGxyYW5nZXIiIH4gImNlbGxyYW5nZXIiKSkgJT4lCiAgZHBseXI6OmxlZnRfam9pbihxdWFudF9pbmZvLAogICAgICAgICAgICAgICAgICAgYnkgPSBjKCJ0b29sIiA9ICJ0b29sIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgInF1YW50X2lkIiA9ICJxdWFudF9kaXIiKSkKCgpnZW5lX2NvdW50cyA8LSByb3dkYXRhX2luZm9fZGYgJT4lIAogICMgcmVtb3ZlIGdlbmVzIHRoYXQgaGF2ZSBhIGxvdyBmcmVxdWVuY3kgb2YgYmVpbmcgZGV0ZWN0ZWQKICBkcGx5cjo6ZmlsdGVyKGRldGVjdGVkID49IDUuMCkgJT4lCiAgZHBseXI6OmNvdW50KGdlbmVfaWQsIHNhbXBsZSkKCmNvbW1vbl9nZW5lcyA8LSBnZW5lX2NvdW50cyAlPiUKICBkcGx5cjo6ZmlsdGVyKG4gPT0gOSkgJT4lCiAgZHBseXI6OnB1bGwoZ2VuZV9pZCkKCnJvd2RhdGFfcWNfY29tbW9uIDwtIHJvd2RhdGFfaW5mb19kZiAlPiUKICBkcGx5cjo6ZmlsdGVyKAogICAgKGdlbmVfaWQgJWluJSBjb21tb25fZ2VuZXMpIAogICkKYGBgCgpgYGB7cn0KIyBzcGxpdCBpbnRvIGNlbGwgYW5kIG51Y2xldXMKY2VsbF9yb3dkYXRhX2NvbW1vbiA8LSByb3dkYXRhX3FjX2NvbW1vbiAlPiUKICBkcGx5cjo6ZmlsdGVyKHNlcV91bml0ID09ICJjZWxsIikKbnVjbGV1c19yb3dkYXRhX2NvbW1vbiA8LSByb3dkYXRhX3FjX2NvbW1vbiAlPiUKICBkcGx5cjo6ZmlsdGVyKHNlcV91bml0ID09ICJudWNsZXVzIiAmIGluZGV4X3R5cGUgPT0gInNwbGljaSIpCmBgYAoKCmBgYHtyfQojIHNwcmVhZCB0YWJsZSBmb3IgY29tcGFyaXNvbnMKY2VsbF9yb3dkYXRhX2NvciA8LSBjZWxsX3Jvd2RhdGFfY29tbW9uICU+JQogIGRwbHlyOjpzZWxlY3QodG9vbCwgaW5kZXhfdHlwZSwgYWxldmluX3Jlc29sdXRpb24sIGFsZXZpbl9hbGlnbm1lbnQsIGdlbmVfaWQsIHNhbXBsZSwgbWVhbikgJT4lCiAgIyBzcHJlYWQgdGhlIG1lYW4gZXhwcmVzc2lvbiBzdGF0cyB0byBvbmUgY29sdW1uIHBlciBjYWxsZXIKICB0aWR5cjo6cGl2b3Rfd2lkZXIoaWRfY29scyA9IGMoZ2VuZV9pZCwgc2FtcGxlKSwKICAgICAgICAgICAgICAgICAgICAgbmFtZXNfZnJvbSA9IGMoInRvb2wiLCAiaW5kZXhfdHlwZSIsICJhbGV2aW5fcmVzb2x1dGlvbiIsICJhbGV2aW5fYWxpZ25tZW50IiksCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlc19mcm9tID0gbWVhbikgJT4lCiAgIyBkcm9wIHJvd3Mgd2l0aCBOQSB2YWx1ZXMgdG8gZWFzZSBjb3JyZWxhdGlvbiBjYWxjdWxhdGlvbnMKICB0aWR5cjo6ZHJvcF9uYSgpCgpudWNsZXVzX3Jvd2RhdGFfY29yIDwtIG51Y2xldXNfcm93ZGF0YV9jb21tb24gJT4lCiAgZHBseXI6OnNlbGVjdCh0b29sLCBpbmRleF90eXBlLCBhbGV2aW5fcmVzb2x1dGlvbiwgYWxldmluX2FsaWdubWVudCwgZ2VuZV9pZCwgc2FtcGxlLCBtZWFuKSAlPiUKICB0aWR5cjo6cGl2b3Rfd2lkZXIoaWRfY29scyA9IGMoZ2VuZV9pZCwgc2FtcGxlKSwKICAgICAgICAgICAgICAgICAgICAgbmFtZXNfZnJvbSA9IGMoInRvb2wiLCAiaW5kZXhfdHlwZSIsICJhbGV2aW5fcmVzb2x1dGlvbiIsICJhbGV2aW5fYWxpZ25tZW50IiksCiAgICAgICAgICAgICAgICAgICAgIHZhbHVlc19mcm9tID0gbWVhbikgJT4lCiAgdGlkeXI6OmRyb3BfbmEoKQpgYGAKCgpOb3cgd2UgY2FuIGxvb2sgYXQgdGhlIGNvcnJlbGF0aW9uIG9mIG1lYW4gZ2VuZSBleHByZXNzaW9uIGFjcm9zcyBlYWNoIG9mIG91ciB0b29sIGNvbmZpZ3VyYXRpb25zIGluIGVhY2ggc2FtcGxlLiAKCmBgYHtyfQpjZWxsX3Jvd2RhdGFfY29yICU+JSAKICBkcGx5cjo6Z3JvdXBfYnkoc2FtcGxlKSAlPiUKICBkcGx5cjo6c3VtbWFyaXplKAogICAgY0ROQV9zYWxpZ25fZnVsbF9jb3IgPSBjb3IoYGNlbGxyYW5nZXJfY0ROQV9ub3RfYWxldmluX25vdF9hbGV2aW5gLCBgYWxldmluLWZyeV9jRE5BX2Z1bGxfc2FsaWduYCwgbWV0aG9kID0gInNwZWFybWFuIiksCiAgICBjRE5BX3NrZXRjaF9mdWxsX2NvciA9Y29yKGBjZWxscmFuZ2VyX2NETkFfbm90X2FsZXZpbl9ub3RfYWxldmluYCwgYGFsZXZpbi1mcnlfY0ROQV9mdWxsX3NrZXRjaGAsIG1ldGhvZCA9ICJzcGVhcm1hbiIpLAogICAgY0ROQV9zYWxpZ25fY3JfY29yID0gY29yKGBjZWxscmFuZ2VyX2NETkFfbm90X2FsZXZpbl9ub3RfYWxldmluYCwgYGFsZXZpbi1mcnlfY0ROQV9jcl9zYWxpZ25gLCBtZXRob2QgPSAic3BlYXJtYW4iKSwKICAgIGNETkFfc2tldGNoX2NyX2NvciA9IGNvcihgY2VsbHJhbmdlcl9jRE5BX25vdF9hbGV2aW5fbm90X2FsZXZpbmAsIGBhbGV2aW4tZnJ5X2NETkFfY3Jfc2tldGNoYCwgbWV0aG9kID0gInNwZWFybWFuIiksIAogICAgc3BsaWNpX3NhbGlnbl9mdWxsX2NvciA9IGNvcihgY2VsbHJhbmdlcl9jRE5BX25vdF9hbGV2aW5fbm90X2FsZXZpbmAsIGBhbGV2aW4tZnJ5X3NwbGljaV9mdWxsX3NhbGlnbmAsIG1ldGhvZCA9ICJzcGVhcm1hbiIpLAogICAgc3BsaWNpX3NrZXRjaF9mdWxsX2NvciA9IGNvcihgY2VsbHJhbmdlcl9jRE5BX25vdF9hbGV2aW5fbm90X2FsZXZpbmAsIGBhbGV2aW4tZnJ5X3NwbGljaV9mdWxsX3NrZXRjaGAsIG1ldGhvZCA9ICJzcGVhcm1hbiIpLAogICAgc3BsaWNpX3NhbGlnbl9jcl9jb3IgPSBjb3IoYGNlbGxyYW5nZXJfY0ROQV9ub3RfYWxldmluX25vdF9hbGV2aW5gLCBgYWxldmluLWZyeV9zcGxpY2lfY3Jfc2FsaWduYCwgbWV0aG9kID0gInNwZWFybWFuIiksCiAgICBzcGxpY2lfc2tldGNoX2NyX2NvciA9IGNvcihgY2VsbHJhbmdlcl9jRE5BX25vdF9hbGV2aW5fbm90X2FsZXZpbmAsIGBhbGV2aW4tZnJ5X3NwbGljaV9jcl9za2V0Y2hgLCBtZXRob2QgPSAic3BlYXJtYW4iKQogICkKYGBgCgpgYGB7cn0KbnVjbGV1c19yb3dkYXRhX2NvciAlPiUgCiAgZHBseXI6Omdyb3VwX2J5KHNhbXBsZSkgJT4lCiAgZHBseXI6OnN1bW1hcml6ZSgKICAgIHNwbGljaV9zYWxpZ25fZnVsbF9jb3IgPSBjb3IoYGNlbGxyYW5nZXJfc3BsaWNpX25vdF9hbGV2aW5fbm90X2FsZXZpbmAsIGBhbGV2aW4tZnJ5X3NwbGljaV9mdWxsX3NhbGlnbmAsIG1ldGhvZCA9ICJzcGVhcm1hbiIpLAogICAgc3BsaWNpX3NrZXRjaF9mdWxsX2NvciA9IGNvcihgY2VsbHJhbmdlcl9zcGxpY2lfbm90X2FsZXZpbl9ub3RfYWxldmluYCwgYGFsZXZpbi1mcnlfc3BsaWNpX2Z1bGxfc2tldGNoYCwgbWV0aG9kID0gInNwZWFybWFuIiksCiAgICBzcGxpY2lfc2FsaWduX2NyX2NvciA9IGNvcihgY2VsbHJhbmdlcl9zcGxpY2lfbm90X2FsZXZpbl9ub3RfYWxldmluYCwgYGFsZXZpbi1mcnlfc3BsaWNpX2NyX3NhbGlnbmAsIG1ldGhvZCA9ICJzcGVhcm1hbiIpLAogICAgc3BsaWNpX3NrZXRjaF9jcl9jb3IgPSBjb3IoYGNlbGxyYW5nZXJfc3BsaWNpX25vdF9hbGV2aW5fbm90X2FsZXZpbmAsIGBhbGV2aW4tZnJ5X3NwbGljaV9jcl9za2V0Y2hgLCBtZXRob2QgPSAic3BlYXJtYW4iKQogICkKYGBgCkp1c3QgbGlrZSB3aXRoIFVNSXMvY2VsbCwgdGhlc2UgY29ycmVsYXRpb25zIGFyZSBxdWl0ZSBoaWdoIGFjcm9zcyBhbGwgb2YgdGhlIHRvb2xzIGFuZCBhbGwgb2YgdGhlIHNhbXBsZXMuIApUaGUgYmlnZ2VzdCBkcm9wIGluIGNvcnJlbGF0aW9uIGRvZXMgc2VlbSB0byBiZSBpbiB0aGUgYHNwbGljaV9zYWxpZ25fZnVsbGAgYW5kIGBzcGxpY2lfc2tldGNoX2Z1bGxgIHZzLiBgY2VsbHJhbmdlcmAgY29tcGFyaXNvbnMgaW4gdGhlIHNpbmdsZS1jZWxsIHNhbXBsZXMgd2l0aCBvbmx5IGFyb3VuZCAwLjk1LTAuOTcgaW4gY29ycmVsYXRpb24gY29lZmZpY2llbnRzLiAKCldlIGNhbiBsb29rIGF0IGEgZmV3IGV4YW1wbGVzIG1vcmUgY2xvc2VseSB0byBzZWUgaG93IHdlbGwgdGhlIG1lYW4gZ2VuZSBleHByZXNzaW9uIGZvciBlYWNoIGdlbmUgYWN0dWFsbHkgbGluZXMgdXAuIAoKYGBge3J9CmdncGxvdChjZWxsX3Jvd2RhdGFfY29yLCBhZXMoeCA9IGBjZWxscmFuZ2VyX2NETkFfbm90X2FsZXZpbl9ub3RfYWxldmluYCwgeSA9IGBhbGV2aW4tZnJ5X2NETkFfZnVsbF9za2V0Y2hgKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDAuNSwgYWxwaGEgPSAwLjEpICsgCiAgZmFjZXRfd3JhcCh+IHNhbXBsZSkgKyAKICBzY2FsZV94X2xvZzEwKCkgKyAKICBzY2FsZV95X2xvZzEwKCkgKyAKICBsYWJzKHggPSAiQ2VsbCBSYW5nZXIgbWVhbiBnZW5lIGV4cHJlc3Npb24iLCB5ID0gIkFsZXZpbiBGcnksIGNETkEgaW5kZXgsIEZ1bGwgUmVzb2x1dGlvbiBTa2V0Y2ggTWVhbiBnZW5lIGV4cHJlc3Npb24iKSArIAogIHRoZW1lX2NsYXNzaWMoKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoY2VsbF9yb3dkYXRhX2NvciwgYWVzKHggPSBgY2VsbHJhbmdlcl9jRE5BX25vdF9hbGV2aW5fbm90X2FsZXZpbmAsIHkgPSBgYWxldmluLWZyeV9jRE5BX2NyX3NrZXRjaGApKSArCiAgZ2VvbV9wb2ludChzaXplID0gMC41LCBhbHBoYSA9IDAuMSkgKyAKICBmYWNldF93cmFwKH4gc2FtcGxlKSArIAogIHNjYWxlX3hfbG9nMTAoKSArIAogIHNjYWxlX3lfbG9nMTAoKSArIAogIGxhYnMoeCA9ICJDZWxsIFJhbmdlciBtZWFuIGdlbmUgZXhwcmVzc2lvbiIsIHkgPSAiQWxldmluIEZyeSwgY0ROQSBpbmRleCwgY3ItbGlrZSBSZXNvbHV0aW9uIFNrZXRjaCBNZWFuIGdlbmUgZXhwcmVzc2lvbiIpICsgCiAgdGhlbWVfY2xhc3NpYygpCmBgYApgYGB7cn0KZ2dwbG90KGNlbGxfcm93ZGF0YV9jb3IsIGFlcyh4ID0gYGNlbGxyYW5nZXJfY0ROQV9ub3RfYWxldmluX25vdF9hbGV2aW5gLCB5ID0gYGFsZXZpbi1mcnlfY0ROQV9jcl9zYWxpZ25gKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDAuNSwgYWxwaGEgPSAwLjEpICsgCiAgZmFjZXRfd3JhcCh+IHNhbXBsZSkgKyAKICBzY2FsZV94X2xvZzEwKCkgKyAKICBzY2FsZV95X2xvZzEwKCkgKyAKICBsYWJzKHggPSAiQ2VsbCBSYW5nZXIgbWVhbiBnZW5lIGV4cHJlc3Npb24iLCB5ID0gIkFsZXZpbiBGcnksIGNETkEgaW5kZXgsIGNyLWxpa2UgUmVzb2x1dGlvbiBzYWxpZ24gTWVhbiBnZW5lIGV4cHJlc3Npb24iKSArIAogIHRoZW1lX2NsYXNzaWMoKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoY2VsbF9yb3dkYXRhX2NvciwgYWVzKHggPSBgY2VsbHJhbmdlcl9jRE5BX25vdF9hbGV2aW5fbm90X2FsZXZpbmAsIHkgPSBgYWxldmluLWZyeV9zcGxpY2lfZnVsbF9za2V0Y2hgKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDAuNSwgYWxwaGEgPSAwLjEpICsgCiAgZmFjZXRfd3JhcCh+IHNhbXBsZSkgKyAKICBzY2FsZV94X2xvZzEwKCkgKyAKICBzY2FsZV95X2xvZzEwKCkgKyAKICBsYWJzKHggPSAiQ2VsbCBSYW5nZXIgbWVhbiBnZW5lIGV4cHJlc3Npb24iLCB5ID0gIkFsZXZpbiBGcnksIHNwbGljaSBpbmRleCwgRnVsbCBSZXNvbHV0aW9uIFNrZXRjaCBNZWFuIGdlbmUgZXhwcmVzc2lvbiIpICsgCiAgdGhlbWVfY2xhc3NpYygpCmBgYAoKYGBge3J9CmdncGxvdChjZWxsX3Jvd2RhdGFfY29yLCBhZXMoeCA9IGBjZWxscmFuZ2VyX2NETkFfbm90X2FsZXZpbl9ub3RfYWxldmluYCwgeSA9IGBhbGV2aW4tZnJ5X3NwbGljaV9jcl9za2V0Y2hgKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDAuNSwgYWxwaGEgPSAwLjEpICsgCiAgZmFjZXRfd3JhcCh+IHNhbXBsZSkgKyAKICBzY2FsZV94X2xvZzEwKCkgKyAKICBzY2FsZV95X2xvZzEwKCkgKyAKICBsYWJzKHggPSAiQ2VsbCBSYW5nZXIgbWVhbiBnZW5lIGV4cHJlc3Npb24iLCB5ID0gIkFsZXZpbiBGcnksIHNwbGljaSBpbmRleCwgY3ItbGlrZSBSZXNvbHV0aW9uIFNrZXRjaCBNZWFuIGdlbmUgZXhwcmVzc2lvbiIpICsgCiAgdGhlbWVfY2xhc3NpYygpCmBgYApgYGB7cn0KZ2dwbG90KGNlbGxfcm93ZGF0YV9jb3IsIGFlcyh4ID0gYGNlbGxyYW5nZXJfY0ROQV9ub3RfYWxldmluX25vdF9hbGV2aW5gLCB5ID0gYGFsZXZpbi1mcnlfc3BsaWNpX2NyX3NhbGlnbmApKSArCiAgZ2VvbV9wb2ludChzaXplID0gMC41LCBhbHBoYSA9IDAuMSkgKyAKICBmYWNldF93cmFwKH4gc2FtcGxlKSArIAogIHNjYWxlX3hfbG9nMTAoKSArIAogIHNjYWxlX3lfbG9nMTAoKSArIAogIGxhYnMoeCA9ICJDZWxsIFJhbmdlciBtZWFuIGdlbmUgZXhwcmVzc2lvbiIsIHkgPSAiQWxldmluIEZyeSwgc3BsaWNpIGluZGV4LCBjci1saWtlIFJlc29sdXRpb24gU2FsaWduIE1lYW4gZ2VuZSBleHByZXNzaW9uIikgKyAKICB0aGVtZV9jbGFzc2ljKCkKYGBgCgoKSW50ZXJlc3RpbmdseSwgd2hlbiB5b3UgbG9vayBhdCB0aGUgY0ROQSBpbmRleCwgeW91IGRvIHNlZSBnZW5lcyB3aXRoIGhpZ2hlciBleHByZXNzaW9uIGZvdW5kIGluIEFsZXZpbi1mcnkgaW4gY29tcGFyZWQgdG8gY2VsbHJhbmdlciAoc2hvd24gYnkgYSBncm91cCB0byB0aGUgdXBwZXIgbGVmdCBvZiB0aGUgZGlhZ29uYWwpLCBidXQgd2hlbiB5b3UgbW92ZSB0byB0aGUgc3BsaWNpIGluZGV4IHRoYXQgZ3JvdXAgZGlzYXBwZWFycy4gCldpdGggdGhlIHNwbGljaSBpbmRleCBhbmQgZnVsbCByZXNvbHV0aW9uIHRoZXJlIHN0aWxsIGFwcGVhcnMgdG8gYmUgc29tZSBpbmNyZWFzZSBpbiBnZW5lIGV4cHJlc3Npb24gZGV0ZWN0ZWQgaW4gQWxldmluLWZyeSwgYnV0IHdpdGggdGhlIGNyLWxpa2UgcmVzb2x1dGlvbiBpdCBhbG1vc3QgbG9va3MgbGlrZSB0aGVyZSBpcyBub3cgbG93ZXIgZGV0ZWN0aW9uIGluIGdlbmUgZXhwcmVzc2lvbiBhbmQgc29tZSBnZW5lcyBoYXZlIGxvc3QgZ2VuZSBleHByZXNzaW9uIChzaG93biB3aXRoIGEgZ3JvdXAgdG8gdGhlIHJpZ2h0IG9mIHRoZSBkaWFnb25hbCBpbiB0aGUgbGFzdCBwbG90KS4gCgpBZGRpdGlvbmFsbHksIHdoZW4geW91IGxvb2sgYXQgdGhlIGAtLXNrZXRjaGAgdnMuIHRoZSBgc2FsaWduYCBhbGlnbm1lbnQsIHRoZXJlIGFwcGVhcnMgdG8gYmUgc29tZSBsb3dlciBleHByZXNzZWQgZ2VuZXMgdGhhdCBwb3AgdXAgb2ZmIHRoZSBkaWFnb25hbCB3aXRoIGAtLXNrZXRjaGAgYW5kIHRoZSBjRE5BIGluZGV4LCBidXQgbm90IGFzIG11Y2ggd2l0aCB0aGUgYHNwbGljaWAgaW5kZXggd2hlbiBsb29raW5nIGF0IHRoZSBgY3ItbGlrZWAgcmVzb2x1dGlvbi4gCgpMZXQncyBsb29rIGF0IHRoZSBzaW5nbGUtbnVjbGVpIGRhdGEuIAoKYGBge3J9CmdncGxvdChudWNsZXVzX3Jvd2RhdGFfY29yLCBhZXMoeCA9IGBjZWxscmFuZ2VyX3NwbGljaV9ub3RfYWxldmluX25vdF9hbGV2aW5gLCB5ID0gYGFsZXZpbi1mcnlfc3BsaWNpX2Z1bGxfc2tldGNoYCkpICsKICBnZW9tX3BvaW50KHNpemUgPSAwLjUsIGFscGhhID0gMC4xKSArIAogIGZhY2V0X3dyYXAofiBzYW1wbGUpICsgCiAgc2NhbGVfeF9sb2cxMCgpICsgCiAgc2NhbGVfeV9sb2cxMCgpICsgCiAgbGFicyh4ID0gIkNlbGwgUmFuZ2VyIG1lYW4gZ2VuZSBleHByZXNzaW9uIiwgeSA9ICJBbGV2aW4gRnJ5LCBzcGxpY2kgaW5kZXgsIEZ1bGwgUmVzb2x1dGlvbiBTa2V0Y2ggTWVhbiBnZW5lIGV4cHJlc3Npb24iKSArIAogIHRoZW1lX2NsYXNzaWMoKQpgYGAKYGBge3J9CmdncGxvdChudWNsZXVzX3Jvd2RhdGFfY29yLCBhZXMoeCA9IGBjZWxscmFuZ2VyX3NwbGljaV9ub3RfYWxldmluX25vdF9hbGV2aW5gLCB5ID0gYGFsZXZpbi1mcnlfc3BsaWNpX2Z1bGxfc2FsaWduYCkpICsKICBnZW9tX3BvaW50KHNpemUgPSAwLjUsIGFscGhhID0gMC4xKSArIAogIGZhY2V0X3dyYXAofiBzYW1wbGUpICsgCiAgc2NhbGVfeF9sb2cxMCgpICsgCiAgc2NhbGVfeV9sb2cxMCgpICsgCiAgbGFicyh4ID0gIkNlbGwgUmFuZ2VyIG1lYW4gZ2VuZSBleHByZXNzaW9uIiwgeSA9ICJBbGV2aW4gRnJ5LCBzcGxpY2kgaW5kZXgsIEZ1bGwgUmVzb2x1dGlvbiBTYWxpZ24gTWVhbiBnZW5lIGV4cHJlc3Npb24iKSArIAogIHRoZW1lX2NsYXNzaWMoKQpgYGAKCgpgYGB7cn0KZ2dwbG90KG51Y2xldXNfcm93ZGF0YV9jb3IsIGFlcyh4ID0gYGNlbGxyYW5nZXJfc3BsaWNpX25vdF9hbGV2aW5fbm90X2FsZXZpbmAsIHkgPSBgYWxldmluLWZyeV9zcGxpY2lfY3Jfc2tldGNoYCkpICsKICBnZW9tX3BvaW50KHNpemUgPSAwLjUsIGFscGhhID0gMC4xKSArIAogIGZhY2V0X3dyYXAofiBzYW1wbGUpICsgCiAgc2NhbGVfeF9sb2cxMCgpICsgCiAgc2NhbGVfeV9sb2cxMCgpICsgCiAgbGFicyh4ID0gIkNlbGwgUmFuZ2VyIG1lYW4gZ2VuZSBleHByZXNzaW9uIiwgeSA9ICJBbGV2aW4gRnJ5LCBzcGxpY2kgaW5kZXgsIGNyLWxpa2UgUmVzb2x1dGlvbiBTa2V0Y2ggTWVhbiBnZW5lIGV4cHJlc3Npb24iKSArIAogIHRoZW1lX2NsYXNzaWMoKQpgYGAKYGBge3J9CmdncGxvdChudWNsZXVzX3Jvd2RhdGFfY29yLCBhZXMoeCA9IGBjZWxscmFuZ2VyX3NwbGljaV9ub3RfYWxldmluX25vdF9hbGV2aW5gLCB5ID0gYGFsZXZpbi1mcnlfc3BsaWNpX2NyX3NhbGlnbmApKSArCiAgZ2VvbV9wb2ludChzaXplID0gMC41LCBhbHBoYSA9IDAuMSkgKyAKICBmYWNldF93cmFwKH4gc2FtcGxlKSArIAogIHNjYWxlX3hfbG9nMTAoKSArIAogIHNjYWxlX3lfbG9nMTAoKSArIAogIGxhYnMoeCA9ICJDZWxsIFJhbmdlciBtZWFuIGdlbmUgZXhwcmVzc2lvbiIsIHkgPSAiQWxldmluIEZyeSwgc3BsaWNpIGluZGV4LCBjci1saWtlIFJlc29sdXRpb24gU2FsaWduIE1lYW4gZ2VuZSBleHByZXNzaW9uIikgKyAKICB0aGVtZV9jbGFzc2ljKCkKYGBgCgoKSGVyZSwgd2Ugc2VlIGEgc2ltaWxhciB0cmVuZCB3aGVyZSB3aXRoIHRoZSBmdWxsIHJlc29sdXRpb24gdGhlcmUgYXJlIGdlbmVzIHdpdGggaW5jcmVhc2VkIGV4cHJlc3Npb24gaW4gQWxldmluLWZyeSwgd2hpbGUgd2l0aCB0aGUgY3ItbGlrZSBpdCBzZWVtcyBtb3JlIGNlbnRlcmVkIGFyb3VuZCB0aGUgZGlhZ29uYWwgd2l0aCBzb21lIGdlbmVzIGhhdmluZyBpbmNyZWFzZWQgZXhwcmVzc2lvbiBhbmQgc29tZSBoYXZpbmcgbG93ZXIgZXhwcmVzc2lvbiB0aGFuIGluIGNlbGxyYW5nZXIuIApUaGVyZSBhbHNvIGRvZXNuJ3Qgc2VlbSB0byBiZSBhbnkgb2J2aW91cyBjaGFuZ2VzIGJldHdlZW4gbWVhbiBnZW5lIGV4cHJlc3Npb24gd2l0aCBgLS1za2V0Y2hgIGFuZCBgc2FsaWduYCBpbiB0aGUgc2luZ2xlLW51Y2xlaSBzYW1wbGVzLiAKCipTb21lIGNsb3NpbmcgdGhvdWdodHM6KgoKU2hvdWxkIHdlIGJlIGV4cGxvcmluZyBzb21lIGRpZmZlcmVudCBmaWx0ZXJpbmcgb3B0aW9ucyB3aXRoIEFsZXZpbi1mcnk/IApJdCBsb29rcyBsaWtlIEFsZXZpbi1mcnkgaXMgZG9pbmcganVzdCBhcyBnb29kIG9mIGEgam9iIGFzIGNlbGxyYW5nZXIsIGJ1dCBzcGVjaWZpY2FsbHkgd2l0aCB0aGUgc2luZ2xlLW51Y2xlaSBkYXRhLCB3ZSBzZWUgbW9yZSBjZWxscyB3aXRoIGxvdyBjb3VudHMgdGhhdCBhcmUgaW4gdGhlIGZpbmFsIGNvdW50cyBtYXRyaXggLSBtYXliZSB0aGlzIGlzbid0IGEgcHJvYmxlbSBhbmQgbG93IGNvdW50IGNlbGxzIHdvdWxkIGdldCByZW1vdmVkIGlkZWFsbHkgYmVmb3JlIGFueSBkb3duc3RyZWFtIGFuYWx5c2lzIGFueXdheXMuIAoKU3BsaWNpIHdpdGggc2luZ2xlLWNlbGwgc2FtcGxlcyBzZWVtcyB0byBiZSBwZXJmb3JtaW5nIHNpbWlsYXJseSB0byB0aGUgb3RoZXIgQWxldmluLWZyeSBtb2RlcyBhbmQgY2VsbHJhbmdlciAtIGl0IGV2ZW4gbG9va3MgbGlrZSBpdCBkb2VzIGRlY3JlYXNlIHNvbWUgZ2VuZSBleHByZXNzaW9uIGluIGdlbmVzIHRoYXQgYXJlIHBvb3JseSBjb3JyZWxhdGVkIGJldHdlZW4gQWxldmluLWZyeSBhbmQgY2VsbHJhbmdlci4gCgpDci1saWtlIGdpdmVzIHNpbWlsYXIgcmVzdWx0cyB0byBjZWxscmFuZ2VyLCBtb3JlIHNvIHRoYW4gdGhlIGZ1bGwgcmVzb2x1dGlvbiwgYWx0aG91Z2ggaXQgZG9lcyBsb29rIGxpa2Ugd2UgY291bGQgYmUgbGVhZGluZyB0byBzb21lIGxvd2VyIGdlbmUgZXhwcmVzc2lvbiBpbiB0aGUgc2luZ2xlLWNlbGwgc2FtcGxlcyB3aXRoIEFsZXZpbi1mcnkuIApJJ20gbm90IHN1cmUgSSBjYW4gY29uZmlkZW50bHkgbWFrZSBhIGRlY2lzaW9uIGhlcmUgb24gd2hpY2ggb25lIHdvdWxkIGJlIHRoZSBhcHByb3ByaWF0ZSBjaG9pY2UgeWV0IChhbHRob3VnaCBJIGNhbiBzYXkgSSB0aGluayBib3RoIHNlZW0gdG8gZG8gd2VsbCkuCg==